​传送门​

看这个样子就觉得非常像 d p dp dp

如果 a i = 1 a_i=1 ai=1,说明这个位置最后需要操作奇数次才会变成 0 0 0

如果 a i = 0 a_i=0 ai=0,说明这个位置最后需要操作偶数次才会变成 0 0 0

而且发现一个性质,如果我们从后面往前推会更简单

我们定义 f [ i ] [ 0 ] f[i][0] f[i][0]为 [ i , n ] [i,n] [i,n]都变成 0 0 0且 [ 1 , i ] [1,i] [1,i]目前翻了偶数次

f [ i ] [ 1 ] f[i][1] f[i][1]为 [ i , n ] [i,n] [i,n]都变成 0 0 0且 [ 1 , i ] [1,i] [1,i]目前翻了奇数次

那么显然 f [ n + 1 ] [ 0 ] = 0 f[n+1][0]=0 f[n+1][0]=0

比如当 a [ i ] = 1 a[i]=1 a[i]=1时,转移为

f [ i ] [ 0 ] = m i n ( f [ i + 1 ] [ 0 ] + 1 , f [ i + 1 ] [ 1 ] + 2 ) f[i][0] = min( f[i+1][0]+1,f[i+1][1]+2 ) f[i][0]=min(f[i+1][0]+1,f[i+1][1]+2)

意思是从 i + 1 i+1 i+1的偶数状态继承过来,那么需要 1 1 1的代价单独把自己翻过来

如果从 i + 1 i+1 i+1的奇数状态改变过来,不仅需要翻 [ 1 , i ] [1,i] [1,i]一次,还需要 1 1 1的代价单独把自己翻过来

其他转移同理,不再累赘

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int n,f[maxn][3],a[maxn];
signed main()
{
cin >> n;
for(int i=1;i<=n;i++) cin >> a[i];
memset( f,20,sizeof f);
f[n+1][0] = 0;
for(int i=n;i>=1;i--)
{
if( a[i] )
{
//自己不翻上一个人翻,自己不翻上一个人不翻
f[i][0] = min( f[i+1][0]+1,f[i+1][1]+2 );
f[i][1] = min( f[i+1][0]+1,f[i+1][1] );
}
else
{
f[i][0] = min( f[i+1][0],f[i+1][1]+1 );
f[i][1] = min( f[i+1][0]+2,f[i+1][1]+1 );
}
}
cout << min( f[1][0],f[1][1] );
}

标称的做法和我稍有不同,不过都是一种思路

考虑从前往右递推

翻到第 i i i个位置时, [ 1 , i − 1 ] [1,i-1] [1,i−1]要么都是 0 0 0,要么都是 1 1 1,否则无法通过改变后面的位置达到目的

这样状态就显然了

f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]是把 [ 1 , i ] [1,i] [1,i]都变成 0 / 1 0/1 0/1的最小花费

转移非常显然,不显然可以看官方题解

​传送门​