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

题面传送门

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

P0 为题中给出的序列,P1P0 取反的结果。

Slr 表示 SlSl+1Sr

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

这里用 P0,i=popcount(i)mod2 来理解这个序列。

首先考虑找到 P0 子串的性质。

Lemma.1:若 TP0 的子串且 |T|>1,则存在拆分点 i[1,|T|),使得 Ti1=P0/1,0i1Ti+1|T|=P0/1,0|T|i1

简单地说,就是可以把 T 拆分成两个串 T1,T2,使得 T2P0/1 的前缀,rev(T1)P0/1 的前缀(rev 是字符串翻转)。

证明的话,设 TP0,lr,那么找到 (l,r]ctz 最大的 i,那么 i1 就是一个合法的拆分点。简单地说,就是找到进位最大的一次,后面的是 PP0,i 的前缀,前面的翻转是 PP0,i1 的前缀。

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

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

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

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

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

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

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

计算拆分点的个数和

考虑 <|S| 的拆分点 i,只需要判断 Si1,Si+1|S| 是否是 P0/1 的前缀即可。

对于 |S| 的拆分点 i,后面一半可以任选 P0/1 的前缀,那么只需算前面有几个满足就行了,也就是计算 SP0,0|S|+k1 中出现几次,这个问题等会说。

计算拆分点个数为 2 的数量

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

  • i<|S|,那么只需暴力枚举 i,判断是否合法即可。
  • i|S|,类似地,等价于计算 SP0,l|S|+1r 中的出现次数。

剩下的问题,就是计算 SP0,lr 中的出现次数了,这个问题见 QOJ P6842,这题还是加强的。

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

那么从 u 开始往后匹配 P0,02k1 等价于 u 往后匹配 P0,02k11,然后再匹配 P1,02k11,倍增计算每个点走过的次数即可。

总时间复杂度 Θ(T|S|2+T|S|logk),可以做到 Θ(T|S|logk)

题外话,上面的 Lemma.2 也说明了答案不会超过 4(|S|+k),所以似乎 mod(109+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 @   A_zjzj  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示