题目
思路
这变化规则是很显然的。所有人都有 40 p t s \tt 40pts 40pts 的水平。
假设原来的字符串是 ⟨ s 1 , s 2 , … , s n , s 1 , s 2 , … , s n ⟩ \langle s_1,s_2,\dots,s_n,s_1,s_2,\dots,s_n\rangle ⟨s1,s2,…,sn,s1,s2,…,sn⟩ ,现在我们要得到一个新的偶数 ⟨ s ′ ⟩ \langle s'\rangle ⟨s′⟩ ,不妨设其对称点为 k k k ,那么有
∀ i ∈ [ 1 , k ] , s i ′ = s i + k ′ \forall i\in[1,k],\;s'_i=s'_{i+k} ∀i∈[1,k],si′=si+k′
由于 n < k n<k n<k (只会变长),推出 2 n − k < k 2n-k<k 2n−k<k 。并且 ⟨ s ⟩ \langle s\rangle ⟨s⟩ 只在原来的基础上增加(也就是前 2 n 2n 2n 个字符完全相同),我们得到必要条件
∀ i ∈ [ 1 , 2 n − k ] , s i = s i + k \forall i\in[1,2n-k],s_i=s_{i+k} ∀i∈[1,2n−k],si=si+k
此时 i + k i+k i+k 也已经取遍了 2 n 2n 2n ,后面都可以自己确定,所以同样充分。
又考虑到 s i = s i + n s_{i}=s_{i+n} si=si+n ,于是也有 s i = s i + k = s i + k − n s_{i}=s_{i+k}=s_{i+k-n} si=si+k=si+k−n
这就是说, k − n k-n k−n 是原偶数的一半的一个周期 即可。显然应该取最大的 b o r d e r \tt border border 。
所以我们只存“偶数”的一半。设当前 b o r d e r \tt border border 长度为 k k k ,则将字符串的前 n − k n-k n−k 位复制到最后去。
不妨用 a b a aba aba 表示原来的字符串。那么新的字符串就是 a b a a b abaab abaab 。注意,这里存储的是 “偶数” 的一半。
观察这个 a b a a b abaab abaab ,你会猜测:新的 b o r d e r \tt border border 就是新加入部分!我们要做的就是大胆猜想,然后将其证明。
假设原来的周期是 a a a ,令 n = k a + r n=ka+r n=ka+r 。设现在的周期是 b b b ,显然应该有 a < b a<b a<b 。因为 b = n b=n b=n 是显然成立的,所以我们反证法说明 b < n b<n b<n 不可行。将字符串的下标从 0 0 0 开始编号。
对于任意 i ∈ [ 0 , a ) i\in[0,a) i∈[0,a) 有 s i = s i + n s_i=s_{i+n} si=si+n ,这是 “偶数” 变化规律。而后 s i + n = s i + n − b s_{i+n}=s_{i+n-b} si+n=si+n−b ,这是周期 b b b ,两个下标都保证非负。继续有 s i + n − b = s i + k a + r − b = s ( i + r − b ) m o d a s_{i+n-b}=s_{i+ka+r-b}=s_{(i+r-b)\bmod a} si+n−b=si+ka+r−b=s(i+r−b)moda ,这是因为 i + n − b < n i+n-b<n i+n−b<n ,在周期 a a a 的打击范围内。总结一下,
∀ i ∈ [ 0 , a ) , s i = s ( i + r − b ) m o d a \forall i\in[0,a), s_{i}=s_{(i+r-b)\bmod a} ∀i∈[0,a),si=s(i+r−b)moda
所以 r − b r-b r−b 是一个周期。或者说 ( b − r ) m o d a (b-r)\bmod a (b−r)moda 是周期。只有一种情况 ( b − r ) m o d a < a (b-r)\bmod a<a (b−r)moda<a 却不会撼动 a a a 的最小周期的地位: b ≡ r b\equiv r b≡r ,得到了零周期。
设 b = m a + r ( m < k ) b=ma+r(m<k) b=ma+r(m<k) 就有 ∀ i ∈ [ 0 , a ) , s i = s i + m a + r = s i + r \forall i\in[0,a),s_i=s_{i+ma+r}=s_{i+r} ∀i∈[0,a),si=si+ma+r=si+r
于是又多出一个 r r r 为周期。如果 r = 0 r=0 r=0 倒也可行,否则只能 b = n b=n b=n 。所以,新的周期是原长,即新 b o r d e r \tt border border 是新加入的部分。如果 r = 0 r=0 r=0 那么原字符串的本质是 a a a ⋯ a aaa\cdots a aaa⋯a ,很蠢。
然后就很简单了。你会发现 S i = S i − 1 + S i − 2 S_i=S_{i-1}+S_{i-2} Si=Si−1+Si−2 ,这里 S i S_i Si 是第 i i i 此操作后的 “偶数” 的一半(是一个字符串),而 + + + 是字符串拼接。
然后查询就查询前缀,就会转化成很多 S i × 1 0 k S_i\times 10^k Si×10k 的求和。而 S i S_i Si 很好预处理嘛。
另外,这也说明 ∣ S i ∣ |S_i| ∣Si∣ 的增长是斐波那契级别的。就是 O ( log n ) \mathcal O(\log n) O(logn) 吧。
只要大家不像我一样脑瘫,忘了可以分开求前缀。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int Mod = 998244353;
inline int qkpow(int_ b,int_ q){
int a = 1; b %= Mod;
for(; q; q>>=1,b=b*b%Mod)
if(q&1) a = a*b%Mod;
return a;
}
const int MaxN = 100005;
char a[MaxN<<1]; // "偶数"
int n, nxt[MaxN<<1];
void input(){
scanf("%s",a+1);
n = strlen(a+1)>>1;
nxt[1] = 0; // 直接嗝屁
for(int i=2,j=0; i<=n; ++i){
while(j && a[j+1] != a[i])
j = nxt[j]; // 跳失配
if(a[j+1] == a[i]) ++ j;
nxt[i] = j; // 经典 KMP
}
}
int_ N[MaxN], B[MaxN], V[MaxN];
int val[MaxN];
int_ query(int t,int_ x){
if(t == 1) return val[x];
if(x <= N[t-1])
return query(t-1,x);
return (V[t-1]*qkpow(10,x-N[t-1])
+ query(t,x-N[t-1]))%Mod;
}
void solve(){
val[0] = 0; // 最初的 hash 值
for(int i=1; i<=n; ++i)
val[i] = (10ll*val[i-1]+a[i]-'0')%Mod;
N[1] = n, B[1] = nxt[n], V[1] = val[n];
V[0] = val[n-B[1]]; // 便于转移
int_ bound; scanf("%lld",&bound);
int tot = 2; // 层数
for(; N[tot-1]<bound; ++tot){
B[tot] = N[tot-1]-B[tot-1];
N[tot] = N[tot-1]+B[tot];
V[tot] = (V[tot-1]
*qkpow(10,B[tot])
+ V[tot-2]) % Mod;
}
for(int q=readint(); q--; ){
int_ l; scanf("%lld",&l);
int_ r; scanf("%lld",&r);
int_ qpg = qkpow(10,r-l+1);
l = query(tot-1,l-1);
r = query(tot-1,r);
r = (r-l*qpg%Mod+Mod)%Mod;
printf("%lld\n",r);
}
}
int main(){
for(int T=readint(); T--; )
input(), solve();
return 0;
}