题解:AT_arc109_f [ARC109F] 1D Kingdom Builder

题意:现在有一个无限长的只有整数的数轴,\((-\infty,0]\) 颜色为黑,\([1,n]\) 颜色会给出,\([n+1,\infty)\) 的颜色为白,现在你可以重复进行以下操作:

  • 选择一种颜色,然后如果当前有棋子的旁边位置的颜色是这种颜色且没放棋子,那么只能放在这些位置上;否则可以随意放。

现在要求 \([1,n]\) 中有些位置必须被放棋子,问最少需要放多少棋子才能覆盖。

做法:

考虑怎么样的结果是可能被放出来的。先手玩感受一下。那么如果有一个位置被放了,之后我们就可以用它去向左向右拓展。那放了一个之后他两侧的颜色就会受到限制,就不能随意放了。

所以我们把我们的放点分成两个部分:

  1. 先放若干点。

  2. 向左向右拓展。

这样显然是可以覆盖所有情况的,对于第二部分直接贪就可以,主要是第一部分放置的限制。

我们先考虑第一个放置,假设放的颜色是黑,有三种情况:

  1. 两侧都是黑色,那么现在就等于我可以随意放白色,那肯定是我现在放若干个极长白色连续段再加上一个不完整的段。如果我要放其他的黑色连续段,那肯定需要先把这一个黑色连续段解决再放,但是这样限制了白色,所以不妨认为是先放白,然后随意放黑色段。

  2. 一侧黑一侧白,那么必须有一侧直接填满,和上面后面的操作类似。

  3. 两侧都是白色,和上面的讨论一样,不如认为是先放白,然后随意放黑色段,同样也是可以拓展出来的。

那么现在我们的结论是,我们允许第一部分中出现:

  • 一次黑黑黑,且只取中间的黑
  • 一段不完整的白
  • 若干段极长的白连续段

考虑对这个东西 dp,\(dp_{i,0/1,0/1,0/1/2/3}\) 代表前 \(i\) 个位置的状态的最小棋子数。其中第一个 \(0/1\) 代表第一种情况是否已经出现,第二个 \(0/1\) 代表第二种情况是否已经出现,第三个 \(0/1/2/3\) 代表下面这几种情况:

\(0\):不被加入。

\(1\):加入第二部分,当前第一部分选择的连续段未满足条件。

\(2\):加入第一部分,目前还没满足条件,但是后面会满足。

\(3\):目前已经满足条件,加入第一部分第二部分皆可。

然后讨论进行转移即可。记得正着做完一边把字符取反再做一遍。因为作者写这篇题解的时候被空调吹得要睡着了所以代码和题解里颜色是相反的。

记得前后加入若干个黑色和白色,给出的之外的也是可以放的。

代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
string s, t, s1 = " ", t1 = " ";
int n, ans = 2e9;
int dp[maxn][2][2][4];
void solve() {
	memset(dp, 0x3f, sizeof(dp));
	dp[0][0][0][0] = 0;
	for (int i = 1; i <= n; i++) {
		if(t[i] != 'o') {
			for (int x = 0; x <= 1; x++)
				for (int y = 0; y <= 1; y++)
					dp[i][x][y][0] = min(dp[i - 1][x][y][0], dp[i - 1][x][y][3]);
		}
		for (int x = 0; x <= 1; x++)
			for (int y = 0; y <= 1; y++)
				dp[i][x][y][1] = min(dp[i][x][y][1], min(dp[i - 1][x][y][0], dp[i - 1][x][y][1]) + 1),
				dp[i][x][y][3] = min(dp[i][x][y][3], dp[i - 1][x][y][3] + 1);
		if(s[i] == 'b') {
			if(s[i - 1] == 'b') {
				if(s[i + 1] == 'b') {
					for (int x = 0; x <= 1; x++)
						for (int y = 0; y <= 1; y++)
							dp[i][x][y][2] = min(dp[i][x][y][2], dp[i - 1][x][y][2] + 1);
				}
				else {
					for (int x = 0; x <= 1; x++)
						for (int y = 0; y <= 1; y++)
							dp[i][x][y][3] = min(dp[i][x][y][3], dp[i - 1][x][y][2] + 1);
				}
			}
			else if(s[i - 1] == 'w') { // 必须要判,否则比如向左无限长会被判成有限
				if(s[i + 1] == 'b') {
					for (int x = 0; x <= 1; x++)
						for (int y = 0; y <= 1; y++)
							dp[i][x][y][2] = min(dp[i][x][y][2], min(dp[i - 1][x][y][0], dp[i - 1][x][y][1]) + 1);
				}
				else {
					for (int x = 0; x <= 1; x++)
						for (int y = 0; y <= 1; y++)
							dp[i][x][y][3] = min(dp[i][x][y][3], min(dp[i - 1][x][y][0], dp[i - 1][x][y][1]) + 1);
				}
			}
			for (int x = 0; x <= 1; x++)
				dp[i][x][1][3] = min(dp[i][x][1][3], min(dp[i - 1][x][0][0], dp[i - 1][x][0][1]) + 1);
		}
		if(s[i] == 'w' && s[i - 1] == 'w' && s[i + 1] == 'w') {
			for (int y = 0; y <= 1; y++)
				dp[i][1][y][3] = min(dp[i][1][y][3], min(dp[i - 1][0][y][0], dp[i - 1][0][y][1]) + 1);
		}
	}
	for (int x = 0; x <= 1; x++)
		for (int y = 0; y <= 1; y++)
			ans = min(ans, dp[n][x][y][0]);
}
int main() {
	cin >> n >> s >> t;
	for (int i = 1; i <= n; i++)
		s1 += 'w', t1 += '_';
	s1 += s, t1 += t;
	for (int i = 1; i <= n; i++)
		s1 += 'b', t1 += '_';
	s1 += ' ', t1 += ' ';
	n = 3 * n;
	s = s1, t = t1;
	solve();
	for (int i = 1; i <= n; i++)
		s[i] = 'b' + 'w' - s[i];
	solve();
	cout << ans << endl;
	return 0;
}
posted @ 2025-10-09 16:53  LUlululu1616  阅读(21)  评论(0)    收藏  举报