AT3575 101 to 010(DP)

折月煮酒 提交于 2019-12-01 11:25:48

满分做法:

这道题蛮难想的,\(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;  
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!