满分做法:
这道题蛮难想的,\(dp[i]\)表示到i位置的最大操作数,\(11111101\)和\(1011111\)这两种情况我们可求出它的最大操作数为长度\(-2\),并发现整个序列的操作数可以拆分成几个小序列的最大操作做次数之和。
所以我们要记录\(l[i]\)表示左边离\(i\)最近的\(0\)的位置,并得出转移:
\(dp[i]=max(dp[i],dp[l[i-2]]+i-l[i-2]-2)\),\(dp[i]=max(dp[i],dp[l[i-2]+1]+i-l[i-2]-1-2)\) (s[i-1]'0'&&s[i-2]'1')。为什么会是两个呢?
因为这个序列可能长这样:\(111111011111101\),这样对于第一个转移来说第二个明显更优,而对于\(001111101\)这样的序列,第一个更优.
\(dp[i]=max(dp[i],dp[l[i]-2]+i-l[i]+2-2)\) (l[i]>1&&s[l[i]-1]=='1'),剩下的直接赋值即可。
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxm=500007;
char s[maxm];
int n;
int dp[maxm];//到i位置的最大操作数
int l[maxm];//在左边离i最近的0的位置
int main()
{
scanf("%d",&n);
scanf("%s",s+1);
for(int i=1;i<=n;i++)
{
if(s[i]=='1') l[i]=l[i-1];
else l[i]=i;
}
for(int i=1;i<=n;i++)
{
dp[i]=dp[i-1];
if(s[i]=='1')
{
if(s[i-1]=='0'&&s[i-2]=='1')
{
dp[i]=max(dp[i],dp[l[i-2]]+i-l[i-2]-2);//100|11111101的贡献为他们的长度-2,
dp[i]=max(dp[i],dp[l[i-2]+1]+i-l[i-2]-1-2);//11111101|11111101
}
else if(l[i]>1&&s[l[i]-1]=='1')
{
dp[i]=max(dp[i],dp[l[i]-2]+i-l[i]+2-2);//101111111
}
}
}
printf("%d\n",dp[n]);
return 0;
}