CodeForces 526D

呵呵,先贴一张图:(这就是我CodeForces的头像(至少现在是))

洛谷题目页面传送门 & CodeForces题目页面传送门

给定字符串(a),求它的每一个前缀,是否能被表示成(m+1)个字符串(A)(m)个字符串(B)交错相连的形式,即求(forall iin[1,|a|],left[exists A,exists B,a_{1sim i}=underbrace{A+B+A+cdots+A+B+A}_{m+1 ext{个}A,m ext{个}B} ight])

(|a|inleft[1,10^6 ight])

考虑把(A+B)看作一个整体,这样问题就转化为了求(a)的每一个前缀是否能被表示成(m)个字符串(S)相连再连上一个(S)的前缀(可以(=varnothing),也可以(=S))。

我们先考虑怎么在短时间内知道一个(a)的前缀是否可以被表示为(m)(S)相连,如果可以就再往后扩展。这是一个非常经典的问题。设要求的是(a)的前缀(a_{1sim i})。首先,得满足(mmid i),于是我们可以枚举(dfrac im),即(|S|)。然后如果(a_{1sim i-frac im}=a_{1+frac imsim i}),那么(a_{1sim i})可以被表示为(m)(S)相连(这个很好证吧,错位相等)。我们可以拿(a_{1+frac imsim i})去匹配(a_{1sim i-frac im}),这个显然可以哈希,而在枚举(|S|)(a_{1sim i-frac im})永远是(a)的前缀,所以也可以Z算法(如果聪明的读者还不知道Z算法是什么,please点击这个)。

接下来要考虑如何往后拓展。这个比较简单,往后拓展的那段子串长度一定(in[0,|S|]),并且要与(a)的前缀匹配。这不正是Z算法的专长吗?(min(|S|,z_{a,m|S|+1}))不就是能往后拓展的最长长度吗?这个最长长度也可以哈希+二分,但那复杂度就带(log)了。对了,能往后拓展最长(z_{a,m|S|+1})个,就意味着(forall iin[m|S|,m|S|+z_{a,m|S|+1}])(a_{1sim i})都能被表示成(m+1)个字符串(A)(m)个字符串(B)交错相连的形式,这是个区间答案赋成(1)的操作,可以用线段树或树状数组维护,但更简单的有差分。最后被赋成(1)的次数若(>0),则答案为(1),否则为(0)

感觉说的不太清楚。。。具体看代码吧(也不一定能看懂啊)

#include<bits/stdc++.h>
using namespace std;
const int N=1000000;
int n/*|a|*/,m/*要被表示成m+1个A与m个B交错相连的形式*/;
char a[N+5];//字符串 
int z[N+1];//z数组 
void z_init(){//Z算法 
	z[1]=n;
	int zl=0,zr=0; 
	for(int i=2;i<=n;i++)
		if(zr<i){
			while(i+z[i]<=n&&a[i+z[i]]==a[1+z[i]])z[i]++;
			if(z[i])zl=i,zr=i+z[i]-1;
		}
		else if(i+z[i-zl+1]<=zr)z[i]=z[i-zl+1];
		else{
			z[i]=zr-i+1;
			while(i+z[i]<=n&&a[i+z[i]]==a[1+z[i]])z[i]++;
			zl=i;zr=i+z[i]-1;
		}
}
int d[N+1];//差分数组 
int main(){
	cin>>n>>m>>a+1;
	z_init();
	for(int i=1;i*m<=n;i++)//枚举|S| 
		if(z[i+1]>=i*(m-1)){//a[1~i*m]可以被表示为m个S相连 
//			cout<<i<<" "<<i*m+min(i,z[i*m+1])<<"
";
			d[i*m]++;//往后拓展的左端点差分数组++ 
			if(i*m+min(i,z[i*m+1])+1<=n)d[i*m+min(i,z[i*m+1])+1]--;//往后拓展的右端点的下一个差分数组-- 
		}
	int now=0;
	for(int i=1;i<=n;i++){
		now+=d[i];//现在now为i的答案被赋成1的次数 
		cout<<!!now;//转为bool值 
	}
	return 0;
}
原文地址:https://www.cnblogs.com/ycx-akioi/p/CodeForces-526D.html