【LOJ3037】「JOISC 2019 Day3」开关游戏(DP)

点此看题面

  • 给定两个长度为(n)(01)(A,B),你可以使用区间赋值和区间取反操作,求把(A)变成(B)的最少操作次数。
  • (nle10^6)

操作顺序

如果直接无脑乱操作肯定不可做,因此我们一定要先探讨一下不同操作之间的关系。

考虑对一个不完整的之后将进行赋值操作的区间先取反肯定是没有意义的,而如果我们要跨越一个之后将进行赋值操作的区间取反,可以视作先赋了个反值再执行取反操作。

也就是说,我们强制所有取反操作发生在所有赋值操作之后,二者可以分开讨论。

对于赋值操作,算一个比较经典的问题,连续一段(0101..0(1))赋值需要的操作次数就等于其中(1)的个数(+1)(1010..1(0))则是(0)的个数(+1))。

对于取反操作,如果两次取反区间相交则完全可以同时去掉它们相交的部分,因此取反区间无交。

动态规划

我们设(f_{i,0/1/2,0/1,0/1})表示对于第(i)位,选择赋值((0/1))或不赋值((2)),是否取反(其实根据当前位的字符以及前一维所选值可以直接判断出这一维,方便起见还是列入状态),如果是赋值的话则下一次换值是否需要代价(因为赋值操作相当于只有第一段和第偶数段需要代价),此时的最小花费。

转移枚举一下这一位和上一位全部三维,共有两种代价:从不赋值到赋值或更换值且这次需要代价则花费(1)的代价,若从不取反到取反也要花费(1)的代价。

代码:(O(n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define Gmin(x,y) (x>(y)&&(x=(y)))
using namespace std;
int n,A[N+5],B[N+5],f[N+5][3][2][2];char a[N+5],b[N+5];
int main()
{
	RI i,j,k,p,q,w;for(scanf("%d%s%s",&n,a+1,b+1),i=1;i<=n;++i) A[i]=a[i]&1,B[i]=b[i]&1;
	for(i=0;i<=n;++i) for(j=0;j<=2;++j) for(k=0;k<=1;++k) for(w=0;w<=1;++w) f[i][j][k][w]=1e9;f[0][2][0][0]=0;//初始化
	for(i=1;i<=n;++i) for(j=0;j<=2;++j) for(k=0;k<=1;++k) if(((j^2?j:A[i])^k)==B[i]) for(p=0;p<=2;++p)//要求操作后的值等于B[i]
		for(q=0;q<=1;++q) for(w=0;w<=1;++w) Gmin(f[i][j][k][j^2&&p^2&&w^(p^j)],f[i-1][p][q][w]+(j^2&&p^j&&!w)+(k&&!q));//两种代价:从不赋值到赋值或更换值且这次需要代价;从不取反到取反
	RI ans=1e9;for(j=0;j<=2;++j) for(k=0;k<=1;++k) for(w=0;w<=1;++w) Gmin(ans,f[n][j][k][w]);return printf("%d
",ans),0;
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/LOJ3037.html