【ARC 077 F - SS】【KMP + 思维题】

考虑对于给定字符串 AA,我们发现对于询问区间 [l,r] ,数据范围为 1lr1018 ,而我们会对 AA 进行 1e100 次操作,询问区间 [l,r] 必定不会超过最后经过 1e100 次操作字符串总长的一半。
所以我们只需要考虑字符串 A 即可。

那么此时字符串应如何变化?
比如字符串 dqvdq
我们设 T 为最长公共后缀(nxt[])部分的字符串,Snxt[] 以外部分的字符串。

T = dq     T = dqv         T = dqvdq
dqvdq -> dqv dq dqv -> dqvdq dqv dqvdq -> …

我们新生成串的 T 一定是之前串的 S
所以我们发现了一个规律,即新生成串等于上一个串 + 上上个串。类似斐波那契数列的规律。

预处理出 nxt 数组,和 A 的字母出现次数前缀和。

查询一个区间的时候差分一下。
区间长度若小于字符串 A 的长度直接查询字母,区间长度若小于 AA 长度,也可直接查询。
否则记录上一次 AS 的长度和字母出现次数。
和斐波那契数列增加长度一样的部分,我们可以直接用 while 计算。
剩余部分递归找出答案。

#include 
#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;
}

你可能感兴趣的:(-----,思维题,-----)