给定一个长 的 01 序列 ,对于每一个 ,求出 划分为 个连续段重排后的最长不下降子序列的最大值。
。
下文称最长不下降子序列为 LNDS。
首先问题等价于划分成 个连续段。
不难发现若 则它们一定会被划分进同一个连续段,那么不妨将它们合并。
于是问题转化为了长度为 的 01 相间序列 且每个 有大小 。
发现由于 LNDS 中相邻两个 相同的元素可以划分进同一段,所以直接做并不好做。那么考虑把不在 LNDS 中的元素删除,并把相邻两个相同元素合并。那么若最后剩下 个元素,则:
- LNDS 的长度为这 个元素的 之和;
- 若 则需要划分 段因为 ;
对于 的情况,不妨直接单独做。所以下面默认 。
不难发现如下性质:
-
若删除边界元素,元素个数会减少 ,否则会减少 ,因为两边的元素会合并;
-
一定不会同时删除 和 ;
那么先枚举 和 有没有被删,则问题转化为要找一些两两不相邻的元素删去,使得删掉的元素的 之和最小。
这是一个经典反悔贪心问题。
不难发现对于未被删除的元素中 最小的元素 ,若 没被删掉则 和 就一定要都被删掉。
那么用链表+小根堆维护,设当前删掉的元素总和为 ,每次找到堆顶 :
-
令 加上 ;
-
找到 的前驱 和后继 :
-
若 和 均存在,则 有可能不被删除,令 ,在堆中加入 。
此时原来的 已被删除,现在的 为 和 合并成的新元素,所以在链表中删除 和 。
-
否则 一定会被删除,则在链表中删除 。
此时 和 一定不会被删除,那么在链表中删除 和 (若存在)。
-
重复 次该过程则 LNDS 中将会有 ( 为 和 中被删除的元素个数)个元素,需要划分为 段,那么开个答案数组每次对 取 即可。
时间复杂度 。
代码如下:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
const int S=300005;
int n;
char str[S];
int m,a[S],val[S];
int pre[S],nxt[S];
bool sta[S];
int ans[S];
inline void del(int x)
{
pre[nxt[x]]=pre[x];
nxt[pre[x]]=nxt[x];
sta[x]=false;
}
inline void slove(int x,int y)
{
for(int i=1;i<=m;i++) val[i]=a[i],pre[i]=i-1,nxt[i]=i+1,sta[i]=true;
int c=0,sm=0;
if(x) c++,sm+=val[1],del(nxt[1]);
if(y) c++,sm+=val[m],del(pre[m]);
del(1),del(m);
priority_queue<pair<int,int>> q;
for(int i=1;i<=m;i++) if(sta[i]) q.push(make_pair(-val[i],i));
ans[m-c-1]=min(ans[m-c-1],sm);
while(m-c-1>=1&&!q.empty())
{
auto u=q.top();
q.pop();
int p=u.second;
if(!sta[p]) continue;
c+=2;
sm+=val[p];
ans[m-c-1]=min(ans[m-c-1],sm);
int l=pre[p],r=nxt[p];
if(l!=0&&r!=m+1)
{
val[p]=val[l]+val[r]-val[p];
q.push(make_pair(-val[p],p));
}
else del(p);
if(l!=0) del(l);
if(r!=m+1) del(r);
}
}
int main()
{
scanf("%d%s",&n,str+1);
for(int i=1;i<=n;i++)
{
if(str[i]=='0')
{
if(m==0||a[m]<0) a[++m]=1;
else a[m]++;
}
else
{
if(m==0||a[m]>0) a[++m]=-1;
else a[m]--;
}
}
for(int i=1;i<=m;i++) if(a[i]<0) a[i]=-a[i];
for(int i=1;i<=n;i++) ans[i]=1e8;
slove(0,0);
slove(1,0);
slove(0,1);
slove(1,1);
for(int i=1;i<=n;i++) ans[i]=n-ans[i];
ans[1]=0;
int c1=0;
for(int i=1;i<=n;i++) c1+=str[i]=='1';
for(int i=1,c0=0;i<=n;i++)
{
c0+=str[i]=='0';
ans[1]=max(ans[1],c0+c1);
c1-=str[i]=='1';
}
for(int i=2;i<=n;i++) ans[i]=max(ans[i],ans[i-1]);
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
printf("\n");
return 0;
}