您的位置:首页 > 其它

NOI2016 优秀的拆分(图解)

2017-12-27 18:53 323 查看
如果一个字符串可以被拆分为 AABB 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的。

例如,对于字符串 aabaabaa,如果令 A=aab,B=a,我们就找到了这个字符串拆分成 AABB的一种方式。

一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=a,B=baa,也可以用 AABB表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。

现在给出一个长度为 n的字符串 S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

以下事项需要注意:

1.出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。

2.在一个拆分中,允许出现 A=B。例如 cccc 存在拆分 A=B=c。

3.字符串本身也是它的一个子串。

95分 O(n^2)暴力哈希处理f[i]以i为起始的AA串数量。

统计答案时O(n^2)暴力枚举AA串,与后面的f【i】匹配计入答案。

这个真的想不出来啊。。我们95分的做法慢在处理AA串的速度上。如果我们能够快速得到以i点为结尾的AA串个数和以i+1点为起始的AA点个数,那就能直接计算答案。

枚举A串的长度,每段长度设置一个计算点,我们发现一个AA串的长度为2*L,所有的长度为2*L的AA串上一定会有2个标记。我们考虑两个标记分别在2个A串上,如果这两个点在一个AA串上,两个点为对应点时分别计算这两个点的LCP和LCS。得到的这两个点的两端区间是相同的。



我们发现这种情况时恰好有一个AA串,以前面那个点(记作pre)-LCS+1为起点的串。

即:LCS+LCP>len时 才会有这样的AA串。



我们发现这两个区间有交时,两个对应点相距距离len。所以pre-LCS+1与当前点now-LCS+1是对应点。

我们完全可以找到这样两个长度为len相同的区间。



如图两端区间。然后这段区间是可以进行平移的。所以开始区间为【pre-LCS+1,pre+LCP-len】。

结束区间自己思考一下吧qwqwq。

然后进行差分最后前缀和就可以快速求出所需了。

#include<bits/stdc++.h>
using namespace std;

#define ll long long

const int MAXN=5e4+5;
const int MOD=19260817;
const int base=31;

int hash[MAXN];
ll mi[MAXN],u[MAXN],v[MAXN];
char s[MAXN];

ll gethash(int l,int r){
ll ans=0;
ans=hash[r]-hash[l-1]*mi[r-l+1];
ans%=MOD;ans+=MOD;ans%=MOD;
return ans;
}

void work(){
int zuo,you;
memset(hash,0,sizeof(hash));
memset(u,0,sizeof(u));
memset(v,0,sizeof(v));
scanf("%s",s+1);
int len=strlen(s+1);
for(int i=1;i<=len;i++)hash[i]=(hash[i-1]*base+s[i])%MOD;
for(int L=1;L*2<=len;L++){
for(int i=L+L;i<=len;i+=L){
int pre=i-L;
if(s[i]!=s[pre])continue;
int l=1,r=L,tmps=0;
while(l<=r){//LCS
int mid=(l+r)>>1;
if(gethash(pre-mid+1,pre)==gethash(i-mid+1,i))l=mid+1,tmps=mid;
else r=mid-1;
}
l=1,r=L;int tmpp=0;
while(l<=r){
int mid=(l+r)>>1;
if(gethash(pre,pre+mid-1)==gethash(i,i+mid-1)) l=mid+1,tmpp=mid;
else r=mid-1;
}
if(tmps+tmpp>L){
u[pre-tmps+1]++;u[pre+tmpp-L+1]--;
v[i-tmps+L]++;v[i+tmpp]--;
}
}
}
ll ans=0;
for(int i=1;i<=len;i++){
u[i]+=u[i-1],v[i]+=v[i-1];
}
for(int i=1;i<len;i++) ans+=v[i]*u[i+1];
printf("%lld\n",ans);
}

int main(){
int t;
mi[0]=1;for(int i=1;i<=30002;i++)mi[i]=(mi[i-1]*base)%MOD;
scanf("%d",&t);
while(t--)work();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: