题意:
定义一个 f(s) 为将字符串
s
后面加上长度最小的串,使得新串仍时一个形如AA
的串。在操作无限次后求 [L,R] 中各个字母出现的次数。保证原串形如AA
。
数据范围:
- 2≤|S|≤2×105
- 1≤l≤r≤1018
算法:
问AK爷怎么做,被怒D这不是傻逼题吗。
考虑串是怎么转移的:
- 设T为字符串S的一个最小border(就是S的一个前缀) 我也不知道这个是不是border
- 那么转移就非常显然了:
- SS -> STST -> STSSTS ……
- 由于是无限次操作,那么只考虑原串的一半也是一样的… 很显然
- S -> ST -> STS -> STSST……
- 观察一下发现转移的字符串竟然时“斐波那契”字符串不知道有没有这种字符串,反正长得像就好了
- 因为斐波那契的第80项左右好像已经大于 1018 所以暴力讨论一下
L,R
的情况就好了
题解写成这样估计又要被D
代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
int n, T, p[N];
char s[N];
long long L, R, num[N][30];
long long fib(long long len, int c) {
if (len <= n) return num[len][c];
if (len <= n * 2) return num[n][c] + num[len - n][c];
long long f1 = num[n][c], f2 = num[n][c] + num[T][c], l1 = n, l2 = n + T;
while (len > l1 + l2) {
long long t = f2;
f2 += f1, f1 = t;
t = l2, l2 += l1, l1 = t;
}
return f2 + fib(len - l2, c);
}
long long calc(long long len, int c) {
if (len <= n) return num[len][c];
if (n % T == 0) {
long long re = 1ll * (len - n) / T * num[T][c] + num[n][c];
len -= n, len %= T;
return re + num[len][c];
}
else return fib(len, c);
}
int main() {
scanf("%s%lld%lld", s + 1, &L, &R);
n = strlen(s + 1) / 2;
p[1] = 0;
for (int j = 0, i = 2; i <= n; i ++) {
for (; j && s[j + 1] != s[i];) j = p[j];
if (s[j + 1] == s[i]) j ++;
p[i] = j;
}
for (int i = 1; i <= n; i ++) {
for (int j = 0; j < 26; j ++) num[i][j] = num[i - 1][j];
num[i][s[i] - 'a'] ++;
}
T = n - p[n];
for (int i = 0; i < 26; i ++) printf("%lld%c", calc(R, i) - calc(L - 1, i), i == 25 ? 10 : 32);
return 0;
}