CodeForces 526D Om Nom and Necklace

蹲街弑〆低调 提交于 2019-12-30 00:42:41

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

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

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

\(|a|\in\left[1,10^6\right]\)

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

我们先考虑怎么在短时间内知道一个\(a\)的前缀是否可以被表示为\(m\)\(S\)相连,如果可以就再往后扩展。这是一个非常经典的问题。设要求的是\(a\)的前缀\(a_{1\sim i}\)。首先,得满足\(m|i\),于是我们可以枚举\(\dfrac im\),即\(|S|\)。然后如果\(a_{1\sim i-\frac im}=a_{1+\frac im\sim i}\),那么\(a_{1\sim i}\)可以被表示为\(m\)\(S\)相连(这个很好证吧,错位相等)。我们可以拿\(a_{1+\frac im\sim i}\)去匹配\(a_{1\sim i-\frac im}\),这个显然可以哈希,而在枚举\(|S|\)\(a_{1\sim 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 i\in[m|S|,m|S|+z_{a,m|S|+1}]\)\(a_{1\sim 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])<<"\n";
            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;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!