Codeforces 1140E DP

题意:给你一个数组,如果数组中的某个位置是-1那就可以填1到m的数字中的一个,但是要遵守一个规则:不能出现长度为奇数回文的子串,问合法的填法有多少种?

思路:不出现长度为奇数的回文子串,只需不出现长度为3的回文子串就可以了,那么i位置和i - 2位置填的数字不能一样。所以,我们可以把这个数组拆成2部分,所有的奇数位置和所有的偶数位置分别成一个串,之后吧两个串的答案乘起来就是答案了。每个串肯定是下列情况中的一种或多种构成。

1:全是-1, 那么明显有k * (k - 1) ^ (n - 1)种答案。

2:有一遍是大于0的数,假设长度为n,那么答案就是(k - 1) ^ n。

3:两边都是大于0的数,这个需要DP预处理后得到答案。

设dp[i][0 / 1]表示长度为i的-1串,当前最后一个-1填的数字与最前面的数字不同/相同,合法的方案数。

转移是这样:dp[i][1] = dp[i - 1][0], 因为i - 1位置填的数字与最前面的不同,所有填一个与最前面位置相同的数字是合法的,所有直接转移。

dp[i][0] = (k - 1) * dp[i - 1][1] + (k - 2) * dp[i - 1][0],前半部分很好理解,加上一个和最前面不等的数就可以了。后半部分,每个数不能和最前面相等,也不能和自己相等,所有是k - 2个转移。

代码:

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const LL mod = 998244353ll;
const int maxn = 200010;
LL dp[maxn][2];
LL a[maxn], b[maxn];
int tot = 0; 
LL m;
int n;
LL qpow(LL x, LL y) {
	LL ans = 1;
	for (; y; y >>= 1) {
		if(y & 1) ans = (ans * x) % mod;
		x = (x * x) % mod;
	}
	return ans;
}
LL solve() {
	int pos = 1, cnt = 0;
	LL ans = 1;
	while(pos <= tot) {
		while(a[pos] > 0 && pos <= tot) {
			if(pos > 1 && a[pos] == a[pos - 1]) return 0;
			pos++;
		} 
		int pre = pos - 1;
		while(a[pos] < 0 && pos <= tot) {
			pos++;
			cnt++;
		}
		if(pre == 0 && pos == tot + 1) return (m * qpow(m - 1, tot - 1)) % mod;
		else if(pre == 0 || pos == tot + 1) ans = (ans * qpow(m - 1, pos - pre - 1)) % mod;
		else {
			int flag = (int)(a[pre] == a[pos]);
			if(flag == 1)
				ans = (ans * dp[pos - pre - 1][0]) % mod;
			else {
				LL tmp = (dp[pos - pre - 1][1] + (((dp[pos - pre - 1][0] * qpow(m - 1, mod - 2)) % mod) * (m - 2)) % mod) % mod;
				ans = (ans * tmp) % mod; 
			} 	
		} 
	}
	return ans;	
}
int main() {
	scanf("%d%lld", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &b[i]);
	}
	dp[1][0] = m - 1;
	dp[1][1] = 0;
	for (int i = 2; i <= n; i++) {
		dp[i][0] = ((dp[i - 1][1] * (m - 1)) % mod + (dp[i - 1][0] * (m - 2)) % mod) % mod;
		dp[i][1] = dp[i - 1][0];
	}
	LL ans1, ans2;
	for (int i = 1; i <= n; i += 2)
		a[++tot] = b[i];
	ans1 = solve();
	tot = 0;
	for (int i = 2; i <= n; i += 2)
		a[++tot] = b[i];
	ans2 = solve();
	printf("%lld\n", (ans1 * ans2) % mod);	
}
 

  

转载于:https://www.cnblogs.com/pkgunboat/p/10836649.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值