给定一个长 的序列 ,你可以删除 中的一些数,但相邻两个数不能被同时删除。设最后得到的序列为 ,你需要最小化 的 LDS(最长严格下降子序列),输出这个最小的 LDS。
,。
原题:P11665 [JOI 2025 Final] 只不过是长的领带 2 / Just Long Neckties 2
首先考虑怎么求一个序列 的 LDS,有两种方法:
- 设 为以 结尾的最长 LDS 的长度;
- 利用经典结论(LDS 相当于可以拆分成 个 LNDS(最长不下降子序列))贪心,维护每个 LNDS 的最后一个数,每次对于所有能接的 LNDS,接末尾数最大的那个,若没有能接的则新开一个,LDS 增加 ;
第一个做法不太好维护,而对于第二个做法,不难发现任意时刻 LNDS 的末尾数字均两两不同,并且值域较小,故可以直接设状态 表示处理完 且保留了 ,LNDS 末尾数字集合为 是否可行。转移是简单的。
考虑优化,对于这种 bool 类型的 dp,一个显然的思路是考虑增加信息量来减少复杂度(将某一维放进 dp 值里)。
挖掘性质:
- 随着转移的进行, 一定是越来越大的;
- 考虑将 中为 的位置写出来,即写下 LNDS 末尾数字的具体值且从小到大排序,设其为序列 ,则由该状态转移来的所有状态 对应的 一定都满足 ,故越转移状态越劣(更有可能使得 LDS 增加);
- 那么对于每一个 ,只有最大的满足 的 是有用的,并且转移时也仅需要对每个 保留当前最大的 来转移。因为若最大 无法转移到 ,则此时 严格劣于 (无法到达更远处且本身状态更劣);
那么直接设 表示 最大的合法的 即可,唯一的问题就是需要处理 的转移,因为加入本来就在 中的值并不会改变 ,但却会使 增加。那么考虑枚举使得该转移停止的 ,显然要求这两个数均不在 中,问题变为找到某个位置 后第一个 的位置 。这个可以直接 预处理,但是空间开不下。
考虑分块优化空间,每 个数分一块,预处理每一块到末尾的 ,这个可以在 内完成。查询时先预处理好 到其所在块末尾的 ,再去枚举 并查询整块部分,这部分时间复杂度 ,总时间复杂度 ,足以通过。
代码如下:
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int S=5000005,B=50,BS=1<<21;
int hb[BS];
int n,a[S];
int tmp[25][25],nxt[S/B+5][25][25];
int f[BS];
#define popc(x) __builtin_popcount(x)
inline int ins(int st,int x)
{
int val=st&(1<<x)-1;
if(val==0) st+=1<<x-1;
else st=st-(1<<hb[val])+(1<<x-1);
return st;
}
inline int quexy(int p,int x,int y)
{
while(p<n&&p%B!=1)
{
if(min(a[p],a[p+1])==x&&max(a[p],a[p+1])==y) return p;
p++;
}
if(p==n) return n+1;
return nxt[(p+B)/B][x][y];
}
inline int que(int p,int st)
{
if(p==n) return n+1;
p++;
int res=n+1;
for(int i=1;i<=21;i++) for(int j=i;j<=21;j++) tmp[i][j]=n+1;
int idx=(p-1)/B+1;
int rb=min(n-1,idx*B);
for(int i=rb;i>=p;i--)
{
int x=min(a[i],a[i+1]),y=max(a[i],a[i+1]);
tmp[x][y]=i;
}
for(int x=1;x<=21;x++)
{
if(st>>x-1&1) continue;
for(int y=x;y<=21;y++)
{
if(st>>y-1&1) continue;
res=min(res,tmp[x][y]);
if(idx*B+1<n) res=min(res,nxt[idx+1][x][y]);
}
}
return res;
}
inline void tmax(int &x,int y){x=max(x,y);}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
for(int i=0;i<BS;i++) for(int j=0;j<21;j++) if(i>>j&1) hb[i]=j;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=21;i++) for(int j=i;j<=21;j++) tmp[i][j]=n+1;
for(int i=n-1;i>=1;i--)
{
int x=min(a[i],a[i+1]),y=max(a[i],a[i+1]);
tmp[x][y]=i;
if(i%B==1) memcpy(nxt[(i+B)/B],tmp,sizeof(tmp));
}
memset(f,-1,sizeof(f));
f[0]=0;
int ans=21;
for(int u=0;u<BS;u++)
{
int &i=f[u];
if(i==-1) continue;
i=que(i,u)-1;
// cout<<u<<" "<<to<<endl;
if(i<n) tmax(f[ins(u,a[i+1])],i+1);
if(i<n-1) tmax(f[ins(u,a[i+2])],i+2);
if(i==n-1||i==n) ans=min(ans,popc(u));
}
cout<<ans<<'\n';
return 0;
}