zl程序教程

您现在的位置是:首页 >  其他

当前栏目

【LOJ 3037】开关游戏(DP)

游戏 DP 开关 LOJ
2023-09-27 14:28:28 时间

开关游戏

题目链接:LOJ 3037

题目大意

给你两个 01 串,分别是初始串和目标串,你可以有三种操作:选择一个区间,把区间里面的都变成 0/1,或者把区间里面的 01 反转。
问你最少要操作多少次把初始串变成目标串。

思路

考虑每个位置最后的结果会受什么印象。
首先是覆盖这个区间的操作,接着一个变成 0 / 1 0/1 0/1 的会取消前面所有的作用,那就是可能是前面有变成 0 / 1 0/1 0/1,再配一个可能的翻转(因为两次的话就相当于抵消了,而且没有必要)

那你可以考虑从左往右考虑,直接记录当前点的覆盖状态(根据是否有覆盖和根据是否有翻转,一共 6 6 6 种状态)。
然后考虑从这个点的 x x x 状态到下一个点的 y y y 状态,要花费多少次数。
那这个因为是区间,所以我们是能继承就继承,要停止就停止,要新增才需要增加操作次数。(类似区间拆成扫描线)
不过要注意的是要判断当前状态是否能使得这个点变成目标串里面的数字。

代码

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N = 1e6 + 100;
int n, a[N], b[N];
int f[N][3][2], ans;
//0:没有,1:强制变成0,2:强制变成1
//0:没有,1:翻转 

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%1d", &a[i]);
	for (int i = 1; i <= n; i++) scanf("%1d", &b[i]);
	
	memset(f, 0x7f, sizeof(f)); ans = f[0][0][0]; f[0][0][0] = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j <= 2; j++)
			for (int k = 0; k <= 1; k++) {
				for (int jj = 0; jj <= 2; jj++)
					for (int kk = 0; kk <= 1; kk++) {
						if (jj == 0 && ((a[i] ^ b[i]) ^ kk)) continue;
						if (jj != 0 && ((jj - 1) ^ kk) != b[i]) continue;
						int now = f[i - 1][j][k];
						if (jj && (jj != j)) now++;
						if (kk && !k) now++;
						f[i][jj][kk] = min(f[i][jj][kk], now);
					}
			}
	}
	
	for (int j = 0; j <= 2; j++)
		for (int k = 0; k <= 1; k++)
			ans = min(ans, f[n][j][k]);
	printf("%d", ans);
	
	return 0;
}