51Nod 算法马拉松28 B题 相似子串 哈希
欢迎访问~原文出处——博客园-zhouzhendong
去博客园看该题解
题目传送门 - 51Nod1753
题意概括
两个字符串相似定义为:
1.两个字符串长度相等
2.两个字符串对应位置上有且仅有至多一个位置所对应的字符不相同
给定一个字符串,每次询问两个子串在给定的规则下是否相似。给定的规则指每次给出一些等价关系,如‘a'=’b',‘b'=’c'等,注意这里的等价关系具有传递性,即若‘a'=’b',‘b'=’c',则‘a'=’c'。
题解
我们弄一个可以通过前缀求得区间某一个字母的排列的哈希。
然后判断两个字符串是否相等,就是把该区间所有的字母的哈希值整合起来。
具体看代码。
然后就是二分。
如果两个串完全相等,那么YES。
我们二分,取一个mid,对于mid来说:
如果 左串1≠左串2 且 右串1≠右串2,那么可以确定是NO了。
否则 如果 左串1≠左串2,那么继续弄左串,否则继续弄右串。
时间复杂度:N*26*logN ≈ 1e8而且常数较大。
但是时限有5s,所以可以过去。
代码
#include <cstring> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; const int L=300000+5; LL p=233333333,mod=1000000007,Inv=474576274; int n,m,a[L],fa[26]; LL ha[L][26],Pow[L]; char str[L]; int getf(int k){ return fa[k]==k?k:fa[k]=getf(fa[k]); } LL gethash(int L,int R){ LL Hash=0; for (int i=0;i<26;i++){ LL x=(ha[R][i]+mod-ha[L-1][i]*Pow[R-L+1]%mod)%mod; Hash=(Hash+x*(getf(i)+1))%mod; } return Hash; } int main(){ scanf("%s",str); n=strlen(str); Pow[0]=1; for (int i=1;i<=n;i++) Pow[i]=Pow[i-1]*p%mod; for (int i=1;i<=n;i++) a[i]=str[i-1]-'a'; // 注意,这里只是对于同一个字母的哈希。最后在汇总的时候,要乘上(字母值+1)防止0的出现。 memset(ha,0,sizeof ha); for (int i=1;i<=n;i++){ for (int j=0;j<26;j++) ha[i][j]=ha[i-1][j]*p%mod; ha[i][a[i]]=(ha[i][a[i]]+1)%mod; } scanf("%d",&m); while (m--){ int k,L1,R1,L2,R2; scanf("%d%d%d%d%d",&k,&L1,&R1,&L2,&R2); for (int i=0;i<26;i++) fa[i]=i; for (int i=1;i<=k;i++){ char ch[5]; scanf("%s",ch); fa[getf(ch[0]-'a')]=getf(ch[1]-'a'); } if (R1-L1+1!=R2-L2+1){ puts("NO"); continue; } if (gethash(L1,R1)==gethash(L2,R2)){ puts("YES"); continue; } bool flag=1; int le=1,ri=R1-L1+1,mid; while (le<ri){ mid=(le+ri)>>1; LL haL1=gethash(L1+le-1,L1+mid-1); LL haL2=gethash(L2+le-1,L2+mid-1); LL haR1=gethash(L1+mid,L1+ri-1); LL haR2=gethash(L2+mid,L2+ri-1); if (haL1!=haL2&&haR1!=haR2){ flag=0; break; } if (haL1!=haL2) ri=mid; else le=mid+1; } puts(flag?"YES":"NO"); } return 0; }