AGC046C Shift

题面

题解

Orz \(\mathsf{\color{black}{c}\color{red}{jrzn}}\)

考虑将每一种方案记在操作数最少的 dp 值上,可以发现 \(k > n\) 是没用的。

\(f_{i, j, k}\) 表示当前考虑到第 \(i\) 位,有 \(j\)\(1\) 从后面被搬到了前面,操作了 \(k\) 次的方案数。

由于从后往前有 \(j\)\(1\) 发生移动,所以第 \(i\) 个位置所对应的字符为 \(s_{i - j}\)

转移考虑当前的字符和你想填的字符,可以有四种转移:

\(s_{i - j + 1}\) 和下一位要填的值相等时,有 \(f_{i + 1, j, k} \gets f_{i, j, k}\)

\(s_{i - j + 1} = 0\) 且下一位要填 \(1\) 时,只能从后面搬运一个 \(1\) 到前面去,于是有 \(f_{i + 1, j + 1, k + 1} \gets f_{i, j, k}\)

\(s_{i - j + 1} = 1\) 且下一位要填 \(0\) 时,可以发现这些 \(1\) 必须要被搬到前面去,记 \(\mathrm {nxt}_{i, j}\) 表示最大的满足 \(s_{i - k + 1} = 0\)\(k\),那么下一个位置实际上是匹配的 \(\mathrm {nxt}_{i, j}\) 这个位置,于是有转移 \(f_{i + 1, \mathrm{nxt}_{i, j}, k} \gets f_{i, j, k}\)

最后计算答案,发现 \(f_{n, i, j}\) 如果要满足条件当且仅当 \(s_{n - i + 1 \cdots n}\) 全是 \(1\) 才能够保证 \(1\) 的个数是对的,那么加上所有满足条件的 dp 值即可。

代码

#include <cstdio>
#include <cstring>

const int N(305), Mod(998244353);
inline int upd(const int &x) { return x + (x >> 31 & Mod); }
inline void Add(int &x, const int &y) { x = upd(x + y - Mod); }
int n, K, f[N][N][N], nxt[N][N];
char s[N];

int main()
{
	std::scanf("%s%d", s + 1, &K);
	n = std::strlen(s + 1), f[0][0][0] = 1;
	for (int i = 0; i < n; i++)
		for (int j = 0, t; j <= i; j++)
		{
			for (t = j; s[i + 1 - t] == '1' && t >= 0; --t);
			nxt[i][j] = t;
		}
	if (K > n) K = n;
	for (int i = 0; i < n; i++)
		for (int j = i; j >= 0; j--)
			for (int k = 0; k <= K; k++)
				if (s[i + 1 - j] == '0')
				{
					Add(f[i + 1][j][k], f[i][j][k]);
					Add(f[i + 1][j + 1][k + 1], f[i][j][k]);
				}
				else
				{
					Add(f[i + 1][j][k], f[i][j][k]);
					if (~nxt[i][j]) Add(f[i + 1][nxt[i][j]][k], f[i][j][k]);
				}
	int ans = 0;
	for (int i = 0; i <= n; i++)
	{
		int c = 0;
		for (int j = n - i + 1; j <= n; j++) c += s[j] - '0';
		if (c == i) for (int j = 0; j <= K; j++) Add(ans, f[n][i][j]);
	}
	std::printf("%d\n", ans);
	return 0;
}
posted @ 2020-11-24 22:28  xgzc  阅读(159)  评论(0编辑  收藏  举报