题意:给你一个数组,如果数组中的某个位置是-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 ", (ans1 * ans2) % mod); }