首页 > 其他分享 >P5362 [SDOI2019] 连续子序列 题解--zhengjun

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

时间:2024-01-21 19:00:14浏览次数:30  
标签:cnt 前缀 zhengjun -- 题解 int 拆分 sim

题面传送门

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

记 \(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,1\sim i} \land T_{i+1\sim |T|}=P_{0/1,1\sim |T|-i}\)。

简单地说,就是可以把 \(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;
}

标签:cnt,前缀,zhengjun,--,题解,int,拆分,sim
From: https://www.cnblogs.com/A-zjzj/p/17978171

相关文章

  • CF-915-F-并查集
    915-F题目大意给定一棵\(n\)个节点的树,节点带权,设函数\(I(x,y)\)等于点\(x\)到点\(y\)的路径上最大的点权与最小的点权之差。求:\[\sum_{i=1}^{n}\sum_{j=i}^{n}I(i,j)\]Solution令函数\(F(i,j)\)等于点\(x\)到点\(y\)的路径上最大的点权,函数\(G(i,j)\)等于点\(x\)到点\(y\)......
  • 设计模式—行为型模式之观察者模式
    设计模式—行为型模式之观察者模式观察者模式(ObserverPattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模......
  • Github图床搭建,结合Picgo与jsdelivr的免费cdn加速,以及部分问题解决方案
    留份文档,便于后续查询===================用到的地址:Github:GitHubPicgo:PicGoisHere|PicGojsdelivr加速地址:https://cdn.jsdelivr.net/gh/Github用户名/仓库名@master===================1.创建一个GitHub仓库:进入你的GitHub首页,在右上角你会找到一个➕,在下拉菜单中......
  • Idea快捷键
    第1组:通用型说明快捷键--------------------------------复制代码-copyctrl+c粘贴-pastectrl+v剪切-cutctrl+x撤销-undoctrl+z反撤销-redoctrl+shift+z保存-saveallctrl+s全选-selectallctrl+a第2组:提高......
  • 在Markdown中使用mermaid画图之流程图
    流程图流程图由流程图方向、节点、节点形状、节点间关系构成声明流程图flowchartLR//flowchart声明为流程图、LR确定流程图从左至右的方向 id1[test1]//id--创建出一个节点、括号内为该节点显示的内容 id2[test2] id3[test3]流程图的方向有以下几种选择:TB-从上到......
  • docker镜像管理
    1.查看镜像[root@centos201~]#dockerimagels#查看现有的镜像列表。REPOSITORYTAGIMAGEIDCREATEDSIZEhello-worldlatestfeb5d9fea6a520monthsago13.3kB[root@centos201~]#[root@centos201~]#[root@centos201~]#doc......
  • P7192 [COCI2007-2008#6] GEORGE 题解
    题目简述给定一张$n$个点$m$条边的无向图,从$u_i\rightarrowv_i$需要用时$w_i$分钟。有一位T先生从$0$时刻按有$g$个点的序列顺序移动,即$v_1\rightarrowv_2\rightarrow\cdots\rightarrowv_g$。还有一位卡车司机Luka从$k$时刻开始从$a$点出发,Luka不......
  • mysql 修改密码
    1、找到根目录登录msql   1)以本机为例,mysql安装目录为:usr/localhost/mysql5.7   2)cd  usr/loclahost/mysql5.7/bin    3)登录    4)查看MySQL用户2、 在mysql5.7版本中存放密码字段为authentication_string,再msyql库中#进入mysql库中mysql>......
  • 工作中的网络知识之二
    工作中的网络知识之二网络性能的因素带宽,时延,丢包率,发送/接收/转发效率编码方式信号经过的设备数,设备距离,介质类型等.以及比较玄学的波动.带宽带宽是一个很重要的性能指标,他决定了网络的承载能力.需要着重说明的是:云计算,电信运营商的带宽都是bit/s但......
  • n皇后
    //#include//#include//usingnamespacestd;////constintmaxn=11;//intn,p[maxn],hashtable[maxn]={false},count=0;////voidgenerateP(intindex){// if(index==n+1){// boolflag=true;// for(inti=1;i<=n;i++)// for(intj=i+1;j<=n;j++)......