P5362 [SDOI2019] 连续子序列 题解--zhengjun

题面传送门

提供一种和其他题解完全不同的解法。

\(P_0\) 为题中给出的序列,\(P_1\)\(P_0\) 取反的结果。

\(S_{l\sim r}\) 表示 \(S_lS_{l+1}\dots S_{r}\)

方便起见,\(P\) 下标从 \(0\) 开始,其余的串都是从 \(1\) 开始。

这里用 \(P_{0,i}=\operatorname{popcount(i)}\mod 2\) 来理解这个序列。

首先考虑找到 \(P_0\) 子串的性质。

Lemma.1:若 \(T\)\(P_0\) 的子串且 \(|T|>1\),则存在拆分点 \(i\in [1,|T|)\),使得 \(T_{i\sim 1}=P_{0/1,0\sim i-1} \land T_{i+1\sim |T|}=P_{0/1,0\sim |T|-i-1}\)

简单地说,就是可以把 \(T\) 拆分成两个串 \(T_1,T_2\),使得 \(T_2\)\(P_{0/1}\) 的前缀,\(\operatorname{rev}(T_1)\)\(P_{0/1}\) 的前缀(\(\operatorname{rev}\) 是字符串翻转)。

证明的话,设 \(T\)\(P_{0,l\sim r}\),那么找到 \((l,r]\)\(\operatorname{ctz}\) 最大的 \(i\),那么 \(i-1\) 就是一个合法的拆分点。简单地说,就是找到进位最大的一次,后面的是 \(P_{P_{0,i}}\) 的前缀,前面的翻转是 \(P_{P_{0,i-1}}\) 的前缀。

Lemma.2:若 \(T\)\(P_0\) 的子串且 \(|T|>1\),那么 \(T\) 的拆分点至多只有两个。

你可以考虑把长度 \(\le n\) 的串都找一遍拆分点并输出,发现这个性质。

证明的话考虑反证就行了,大家有兴趣可以自己证明。

同时,如果你打过拆分点的表,会发现如果 \(T\) 有两个拆分点 \(i,j\),那么 \(|i-j|=2^k\),这个很好理解,中间这一段正反都要是 \(P_{0/1}\) 的前缀。

有了这些性质,你会发现一个串 \(T\)\(P_{0}\) 的子串的充要条件就是 \(|T|=1\)\(T\) 存在拆分点。

\(|T|=1\) 的情况只需要特判 \(|S|=1,k=0\) 的情况即可,下面考虑 \(|T|>1\)

那么答案就是在所有前缀为 \(S\) 且长度为 \(|S|+k\)\(01\) 串中【拆分点的个数】-【拆分点个数为 \(2\) 的个数】。

计算拆分点的个数和

考虑 \(<|S|\) 的拆分点 \(i\),只需要判断 \(S_{i\sim 1},S_{i+1\sim |S|}\) 是否是 \(P_{0/1}\) 的前缀即可。

对于 \(\ge|S|\) 的拆分点 \(i\),后面一半可以任选 \(P_{0/1}\) 的前缀,那么只需算前面有几个满足就行了,也就是计算 \(S\)\(P_{0,0\sim |S|+k-1}\) 中出现几次,这个问题等会说。

计算拆分点个数为 \(2\) 的数量

考虑枚举拆分点之间的距离 \(2^t\),那么这种情况下,要求 \(T\) 的两个拆分点 \(i,j\) 满足 \(i+2^t=j,i-1\in [1,2^t],|T|-j \in [1,2^t]\)

  • \(i < |S|\),那么只需暴力枚举 \(i\),判断是否合法即可。
  • \(i\ge |S|\),类似地,等价于计算 \(S\)\(P_{0,l-|S|+1\sim r}\) 中的出现次数。

剩下的问题,就是计算 \(S\)\(P_{0,l\sim r}\) 中的出现次数了,这个问题见 QOJ P6842,这题还是加强的。

大概思路就是要建出 \(S\) 的失配树。

那么从 \(u\) 开始往后匹配 \(P_{0,0\sim 2^k-1}\) 等价于 \(u\) 往后匹配 \(P_{0,0\sim 2^{k-1}-1}\),然后再匹配 \(P_{1,0\sim 2^{k-1}-1}\),倍增计算每个点走过的次数即可。

总时间复杂度 \(\Theta(T|S|^2+T|S|\log k)\),可以做到 \(\Theta(T|S|\log k)\)

题外话,上面的 Lemma.2 也说明了答案不会超过 \(4(|S|+k)\),所以似乎 \(\mod (10^9+9)\) 显得有点多余。

代码

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define all(a) (a).begin(),(a).end()
#ifdef DEBUG
template<class T>
ostream& operator << (ostream &out,vector<T> a){
	out<<'[';
	for(T x:a)out<<x<<',';
	return out<<']';
}
template<class T>
vector<T> ary(T *a,int l,int r){
	return vector<T>{a+l,a+1+r};
}
template<class T>
void debug(T x){
	cerr<<x<<endl;
}
template<class T,class...S>
void debug(T x,S...y){
	cerr<<x<<' ',debug(y...);
}
#else
#define debug(...) void()
#endif
const int N=1e2+10,K=60,mod=1e9+9;
int T,n;
ll k;
char a[N],b[N];
int pre[N][2],suf[N][2];
int ch[N][2],fail[N];
void getfail(){
	for(int i=1;i<=n;i++)b[n+1-i]=a[i];
	b[n+1]=0;
	for(int i=1;i<=n;i++)ch[i-1][b[i]&1]=i;
	for(int i=1;i<=n;i++){
		for(int c:{0,1}){
			int &v=ch[i][c],x=ch[fail[i]][c];
			if(v)fail[v]=x;
			else v=x;
		}
	}
}
int to[K][N][2];
void init(){
	for(int i=0;i<=n;i++){
		for(int c:{0,1}){
			to[0][i][c]=ch[i][c];
		}
	}
	for(int i=1;i<K;i++){
		for(int u=0;u<=n;u++){
			for(int c:{0,1}){
				int t=to[i-1][u][c];
				to[i][u][c]=to[i-1][t][!c];
			}
		}
	}
}
ll calc(ll l,ll r){
	// int ans=0;
	// for(int i=0,u=0;i<r;i++){
	// 	u=ch[u][__builtin_parity(i)];
	// 	ans+=i>=l-1&&u==n;
	// }
	// for(int i=0,u=0;i<r;i++){
	// 	u=ch[u][!__builtin_parity(i)];
	// 	ans+=i>=l-1&&u==n;
	// }
	// debug(l,r,b+1,ans);
	// return ans;
	ll las=l;
	r++;
	static ll cnt[K][N][2];
	memset(cnt,0,sizeof cnt);
	int u[2]={0,0};
	for(int i=K-1,c=0;i>=0;i--){
		if(l>>i&1){
			for(int x:{0,1}){
				u[x]=to[i][u[x]][c^x];
			}
			c^=1;
		}
	}
	for(int i=0;i<K;i++){
		if(l+(1ll<<i)<=r&&(l>>i&1)){
			int c=__builtin_parityll(l);
			for(int x:{0,1}){
				cnt[i][u[x]][c^x]++,u[x]=to[i][u[x]][c^x];
			}
			l+=1ll<<i;
		}
	}
	for(int i=K-1;i>=0;i--){
		if(l+(1ll<<i)<=r&&(~l>>i&1)){
			int c=__builtin_parityll(l);
			for(int x:{0,1}){
				cnt[i][u[x]][c^x]++,u[x]=to[i][u[x]][c^x];
			}
			l+=1ll<<i;
		}
	}
	for(int i=K-1;i>=1;i--){
		for(int u=0;u<=n;u++){
			for(int c:{0,1}){
				int t=to[i-1][u][c];
				cnt[i-1][u][c]+=cnt[i][u][c];
				cnt[i-1][t][!c]+=cnt[i][u][c];
			}
		}
	}
	// debug(las,r-1,b+1,cnt[0][n][0]+cnt[0][n][1]);
	return cnt[0][n][0]+cnt[0][n][1];
}
void get(){
	scanf("%s%lld",a+1,&k),n=strlen(a+1);
	if(n==1&&!k)return puts("1"),void();
	for(int i=1;i<=n;i++){
		for(int c:{0,1}){
			pre[i][c]=1;
			for(int j=0;j<i;j++){
				pre[i][c]&=__builtin_parity(j)^c==a[i-j]-'0';
			}
			suf[i][c]=1;
			for(int j=0;j<=n-i;j++){
				suf[i][c]&=__builtin_parity(j)^c==a[i+j]-'0';
			}
		}
	}
	getfail(),init();
	ll ans=0;
	for(int i=1;i<n;i++){
		ans+=(pre[i][0]+pre[i][1])*(suf[i+1][0]+suf[i+1][1]);
		// debug(i,pre[i][0]+pre[i][1],suf[i+1][0]+suf[i+1][1],ans);
	}
	ans+=calc(0,n+k-1)*2;
	// debug(ans);
	for(ll len=1;len+2<=n+k;len<<=1){
		ll l=max(len+1,n+k-len),r=min(len+len,n+k-1ll);
		if(l>r)continue;
		// debug(n+k,len,l,r);
		ans-=calc(l,r);
		for(ll i=l;i<n;i++){
			ans-=(pre[i][0]+pre[i][1])*(suf[i-len+1][0]+suf[i-len+1][1]);
		}
	}
	printf("%lld\n",ans%mod);
}
void clr(){
	for(int i=0;i<=n;i++){
		fail[i]=ch[i][0]=ch[i][1]=0;
	}
}
int main(){
	freopen(".in","r",stdin);
	// freopen(".out","w",stdout);
	for(scanf("%d",&T);T--;clr())get();
	return 0;
}
posted @ 2024-01-21 18:55  A_zjzj  阅读(27)  评论(0编辑  收藏  举报