AT4928 [AGC033E] Go around a Circle

题目链接

看起来很不可做的样子,但是要求任意位置都存在合法操作序列,看起来限制挺强的,我们尝试把这个限制转化为容易搞的条件。

一种颜色

如果 \(S\) 只有一种颜色,那么我们只要要求另一种颜色不连续即可(因为只要每个点都有一个相邻的 \(S\) 中的那个颜色,那么就可以借此反复横跳)于是答案为

\[\sum_{k \ge 0}{n - k + 1 \choose k} - {n - k - 1 \choose k -2} \]

当然如果 \(n = 1\),即使另一种颜色不相邻也不行,需要特判一下。

两种颜色

现在我们考虑更普遍的情况。假设第一种颜色为 \(R\)。那么我们有如下要求:

不存在相邻的 B

显然,如果存在相邻的 B,那么中间的那个点就不合法了。

于是我们可以将环看做若干形如 RRR...RRB 的“段”。

每段的长均为偶数(R连续段均为奇数)

这个主要是为了应对 S 中的第一段 R 后面的那个 B。我们需要在前面的那些 R 以后一种到 B 的旁边,如果存在一 R 连续段 的长为偶数,那么最终的奇偶性就肯定不对了。手玩一下就好了。

段长不超最短奇R段长

定义“最短奇R段”表示 S 中长为奇数且最短的 R 连续段,那么我们要求每个段长不超过最短奇R段长。

对于 B 连续段,我们可以在 B 的旁边反复横跳。

对于 R 连续段,我们需要在 R 的旁边反复横跳,但是要求最终要跳到 B 的旁边。如果此 R 连续段长度为偶数,那么直接在挨着 B 的那个 R 旁边反复横跳即可;如果为奇数,我们就需要调到 R 连续段的另一头,于是要求此 R 连续段不能太长。

当然,如果这一段是最后一段,则没有这一要求。

段长不超起始R段长

我们要保证一开始点要有足够的时间跳到 B 的旁边,因此需要对段长进行限制。由于每一 R 连续段长度均为奇数,极限数据并不在段的中间,而在段的一边。

仔细思考,发现这三个要求已经足够了。于是直接将题意转化成:

将长为 \(n\) 的环划分成若干段,每一段的长度不能超过 \(k\),且只能在奇数处划分,的方案数。

于是直接大力 DP 即可。然而环上 DP 毕竟还是有些困难,我们枚举 \(1\) 号点的右边的那个边所在的“段”的长度 \(i\),答案为 \(\sum_{i}i \times f_i\)。其中 \(f_i\) 表示序列上将长为 \(i\) 的序列分段的方案数。这个就是经典的 DP 问题了。

\(Code:\)

namespace jzp1 {
	ll jie[N], jieni[N];
	inline ll get_c(int n, int m)
	inline ll quickpow(ll x, int k) 
	inline void sol() {
		if (n == 1) { puts("1"); return ; }
		jie[0] = jieni[0] = 1;
		int up = n + 1;
		for (int i = 1; i <= up; ++i)	jie[i] = jie[i - 1] * i % P;
		jieni[up] = quickpow(jie[up], P - 2);
		for (int i = up - 1; i; --i)	jieni[i] = jieni[i + 1] * (i + 1) % P;
		ll res = 0;
		for (int i = 0; i <= n; ++i) {
			res = (res + get_c(n - i + 1, i) - get_c(n - i - 1, i - 2)) % P;
		}
		printf("%lld\n", (res % P + P) % P);
	}
}
ll f[N];
ll sum[N];
int main() {
	read(n), read(m);
	scanf("%s", s + 1);
	bool flag = false;
	int tmp = 0;
	for (int i = 1; i <= m; ++i)	if (s[i] != s[1]) { flag = true; tmp = i - 1; break; }
	if (!flag) { jzp1::sol(); return 0; }
	if (n & 1) { puts("0"); return 0; }
	int lst = 1, mn = tmp | 1;
	for (int i = 2; i <= m; ++i) {
		if (s[i] != s[1]) {
			if (lst & 1)	MIN(mn, lst);
			lst = 0;
			continue;
		}
		if (s[i] == s[i - 1])	++lst;
		else {
			if (lst & 1) MIN(mn, lst);
			lst = 1;
		}
	}
	n >>= 1;
	int k = (mn + 1) >> 1;
	MIN(k, n);
	f[0] = 1; sum[0] = 1;
	for (int i = 1; i <= n; ++i) {
		if (i <= k)	f[i] = sum[i - 1];
		else f[i] = (sum[i - 1] - sum[i - k - 1]) % P;
		sum[i] = (sum[i - 1] + f[i]) % P;
	}
	ll ans = 0;
	for (int i = 1; i <= k; ++i) {
		ans = (ans + f[n - i] * i) % P;
	}
	ans = (ans << 1) % P;
	printf("%lld\n", (ans % P + P) % P);
	return 0;
}
posted @ 2020-09-22 12:58  JiaZP  阅读(307)  评论(0编辑  收藏  举报