考虑对于给定字符串 AA
,我们发现对于询问区间
[l,r]
,数据范围为
1≤l≤r≤1018
,而我们会对 AA
进行
1e100
次操作,询问区间
[l,r]
必定不会超过最后经过
1e100
次操作字符串总长的一半。
所以我们只需要考虑字符串 A
即可。
那么此时字符串应如何变化?
比如字符串 dqvdq
。
我们设 T
为最长公共后缀(nxt[]
)部分的字符串,S
为 nxt[]
以外部分的字符串。
T = dq T = dqv T = dqvdq
dqvdq -> dqv dq dqv -> dqvdq dqv dqvdq -> …
我们新生成串的 T
一定是之前串的 S
。
所以我们发现了一个规律,即新生成串等于上一个串 + 上上个串。类似斐波那契数列的规律。
–
预处理出 nxt
数组,和 A
的字母出现次数前缀和。
查询一个区间的时候差分一下。
区间长度若小于字符串 A
的长度直接查询字母,区间长度若小于 AA
长度,也可直接查询。
否则记录上一次 A
和 S
的长度和字母出现次数。
和斐波那契数列增加长度一样的部分,我们可以直接用 while
计算。
剩余部分递归找出答案。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
ll l, r;
int len, S, T;
int ans[30][N], nxt[N];
char A[N];
ll cal(int a, ll now){
if (now <= len) return ans[a][now];
if (now <= 2 * len) return ans[a][len] + ans[a][now - len];
ll f1 = ans[a][S], f2 = ans[a][len], lenf1 = S, lenf2 = len;
while (now > lenf1 + lenf2) {
ll tem = f2, lentem = lenf2;
f2 += f1; lenf2 += lenf1;
f1 = tem; lenf1 = lentem;
}
return f2 + cal(a, now - lenf2);
}
int main(){
scanf("%s", A + 1);
scanf("%lld%lld", &l, &r);
len = strlen(A + 1) / 2;
nxt[1] = 0;
for (int i = 2; i <= len; i ++) {
int j = nxt[i - 1];
while(j != 0 && A[j + 1] != A[i]) j = nxt[j];
if(A[j + 1] == A[i]) nxt[i] = j + 1;
else nxt[i] = 0;
}
T = nxt[len];
S = len - T;
for (int i = 1; i <= 26; i ++) {
for (int j = 1; j <= len; j ++) {
ans[i][j] = ans[i][j - 1];
if ((int)A[j] - 96 == i) ans[i][j] ++;
}
}
for (int i = 1; i <= 26; i ++)
printf("%lld ", cal(i, r) - cal(i, l - 1));
printf("\n");
return 0;
}