题目描述
难度分:1600
输入n、m、k(1≤n,m,k≤2000)。
输出有多少个长为n的字符串s,满足s中的每个长为k的连续子串都是回文串。其中m为字母表的大小,答案模109+7。
输入样例1
1 1 1
输出样例1
1
输入样例2
5 2 4
输出样例2
2
算法
并查集+组合数学
比较容易想,分为k为奇数和k为偶数两种情况。如果是前者,那么回文串两翼之间的距离至少是1,随着距离中心越远,距离就会以2为步长递增,变为3、5、7……
因此对于i,假设它是回文串的左翼,它对应的右翼是j(因为i和j之间至少要有一个字符,所以j从i+2开始取),则有[i,j]的长度j−i+1为奇数。只要i左边和j的右边有足够的字符使得[i,j]作为中心,加上旁边相同的字符数可以构成长度为k的串,那么i和j位置就应该是同一个字符。
到这里就很容易想到,把肯定是同一个字符的所有位置合并成一个连通块,用并查集来解决本题。假设最后有cnt个连通块,每个连通块可以取m种字母,总的方案数就是mcnt,用快速幂求一下返回就可以了。同理可以分析k为偶数的情况,只不过j−i+1是偶数,j从i+1开始取。
复杂度分析
时间复杂度
遍历i,对每个i枚举j,时间复杂度是O(n)。而对于可以配对的(i,j),需要进行并查集合并操作,时间复杂度大概是O(logn)级别。而后面组合数的计算就是个快速幂,在最差情况下cnt=n,时间复杂度为O(log2n)。因此,整个算法的时间复杂度为O(n2logn+log2n)。
空间复杂度
空间瓶颈在于并查集的根节点数组,它的大小是O(n)的。而并查集的递归操作对于O(n)来说可以忽略,因此整个算法的额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2010, MOD = 1e9 + 7;
int n, m, k, cnt, p[N];
int fast_power(int a, int b, int p) {
int res = 1 % p;
while(b) {
if(b & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
b >>= 1;
}
return res;
}
int find(int x) {
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
void merge(int x, int y) {
int rx = find(x), ry = find(y);
if(rx != ry) {
p[rx] = ry;
cnt--;
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
if(k == 1) {
printf("%d\n", fast_power(m, n, MOD));
return 0;
}
for(int i = 1; i <= n; i++) {
p[i] = i;
}
cnt = n;
for(int i = 1; i <= n; i++) {
for(int j = i + 1 + (k&1); j <= min(n, i + k - 1); j += 2) {
int half = (k - (j - i + 1))>>1;
if(i - 1 >= half && n - j >= half) {
merge(i, j);
}
}
}
printf("%d\n", fast_power(m, cnt, MOD));
return 0;
}