题目链接:E - RLE (atcoder.jp)
题目大意:给定一个字符串(由小写字母组成)转换方法如下:
->
->
->
->
转换方法大概就是这样,详细的文字说明就八写了。总之就是把连续的字母转化成字母+字母个数的形式。注意 和 的长度是不一样的。现要统计出所有长度为 的字符串中转换之后的长度严格小于 的数量。
题解:考虑使用 求解。 :原串长度为 ,转换后长度为 的数量。
我们可以考虑在一个字符串后面接一个长度为 的串,接上的串所有字符相同且与原串最后一个字符不同。递推式如下:
含义:考虑接上的串的长度 ,因为所有字符相同,发现该串的生成串长度只有 种可能:
直接实现代码,时间复杂度为
for (int i = 1; i <= n; i++)
for (int j = 1; j <= 2*i; j++) //生成串最长可能为原串两倍
for (int k = 1; k <= i && get(k) <= j; k++)
f[i][j] += f[i-k][j - get(k)] * 25; //接上的串字符于之前的串最后一个字符不同,即25种可能性
但是本体数据范围是 ,所以直接暴力显然是无法通过本题的,接下来考虑优化。
我们观察到
可以把 分成许多个区间,即 ,对于同一区间里的 ,所对应的 是一样的,所以得到:
类似于 的反向操作,即输入生成串的长度,返回原串的最短长度。
现在我们只需要用前缀和数组 来维护前缀信息,即
这样就可以把 这样的 相同的区间和转化如下:
因为最多只会有 个 不同的区间和,所以时间复杂度为
完整代码如下:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3010;
int f[N][2*N], n, p, pref[N][2*N];
int get(int x) {
if(1 <= x && x < 10) return 2;
else if(x >= 10 && x <= 99) return 3;
else if(x >= 100 && x <= 999) return 4;
else if(x >= 1000 && x <= 9999) return 5;
}
int got(int x) {
if(x == 2) return 9;
if(x == 3) return 99;
if(x == 4) return 999;
if(x == 5) return 9999;
}
signed main() {
cin >> n >> p;
for(int i=1; i<=n; i++) {
f[i][get(i)] = 26;
pref[i][get(i)] = pref[i-1][get(i)] + 26;
}
for (int i = 1; i <= n; i++)
for (int j = 1; j <= 2*i; j++) {
int k, S = i-1, t2 = 0, SS = i;
for (k = 2; k <= j && got(k) < SS; k++) {
f[i][j] = ((((pref[S][j-k] - pref[SS-got(k)-1][j-k]) + p) % p * 25) % p + f[i][j]) % p;
S = SS - got(k)-1;
}
f[i][j] = ((((pref[S][j-k] - pref[0][j-k]) + p) % p * 25) % p + f[i][j]) % p;
pref[i][j] = (pref[i-1][j] + f[i][j]) % p;
}
int res = 0;
for (int j = 1; j < n; j++)
res = (res + f[n][j]) % p;
cout << res << '\n';
}