偶数

38 篇文章 0 订阅

题目

传送门 to nowcoder

思路

这变化规则是很显然的。所有人都有 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 2nk<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,2nk],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+kn

这就是说, k − n k-n kn 是原偶数的一半的一个周期 即可。显然应该取最大的 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 nk 位复制到最后去。

不妨用 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+nb ,这是周期 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+nb=si+ka+rb=s(i+rb)moda ,这是因为 i + n − b < n i+n-b<n i+nb<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+rb)moda

所以 r − b r-b rb 是一个周期。或者说 ( b − r )   m o d   a (b-r)\bmod a (br)moda 是周期。只有一种情况 ( b − r )   m o d   a < a (b-r)\bmod a<a (br)moda<a 却不会撼动 a a a 的最小周期的地位: b ≡ r b\equiv r br ,得到了零周期。

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 aaaa ,很蠢。

然后就很简单了。你会发现 S i = S i − 1 + S i − 2 S_i=S_{i-1}+S_{i-2} Si=Si1+Si2 ,这里 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值