[51nod 1753] 相似子串
问题描述
两个字符串相似定义为:
1.两个字符串长度相等
2.两个字符串对应位置上有且仅有至多一个位置所对应的字符不相同
给定一个字符串,每次询问两个子串在给定的规则下是否相似。给定的规则指每次给出一些等价关系,如‘a'=’b',‘b'=’c'等,注意这里的等价关系具有传递性,即若‘a'=’b',‘b'=’c',则‘a'=’c'。
输入格式
第一行一个字符串s(1<=|s|<=300000)
第二行一个整数T(1<=T<=300000)
对于每次询问:
第一行5个整数k,l1,r1,l2,r2,表示有k个等价规则,询问的是子串[l1,r1],l2,r2
接下来k行每行两个连续的字符表示这两个字符等价。
此题中所有的字符均为小写字母。
输出格式
T行,若相似则输出“YES”否则输出“NO”
样例输入
abac
3
1 1 2 3 4
bc
1 1 2 3 4
ac
1 1 2 2 3
ac
样例输出
YES
YES
NO
解析
如果想要得到两个字符串是否相似,也就是说允许有一个字符不一样,那么不妨分为左边和右边两部分分别比较。如果两边完全一样,自然原来的两个字符串也是相似的;如果两边都不一样,那么不可能做到只有一个字符不一样,也就是说两个字符串不相似。当只有一边是一样的时候,就去验证不一样的那一半,这就是一个子问题了。
接下来的问题是如何比较两个字符串是否相等。可以用哈希来解决。由于会出现相同规则的更改,用并查集维护相同字母的关系,然后分字母进行哈希,最后计算时在合并即可。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
#define N 300002
using namespace std;
const int p=199999;
const int mod=1000000007;
int t,l,i,j,fa[30],k,l1,r1,l2,r2,hash[N][30],poww[N];
char s[N];
int id(char x)
{
return (int)(x-'a'+1);
}
int find(int x)
{
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
int Hash(int l,int r)
{
int ans=0;
for(int i=1;i<=26;i++){
int tmp=(hash[r][i]-hash[l-1][i]*poww[r-l+1]%mod+mod)%mod;
ans=(ans+tmp*(find(i)+1)%mod)%mod;
}
return ans;
}
signed main()
{
cin>>s>>t;
l=strlen(s);
poww[0]=1;
for(i=1;i<=l;i++) poww[i]=poww[i-1]*p%mod;
for(i=1;i<=l;i++){
for(j=1;j<=26;j++) hash[i][j]=hash[i-1][j]*p%mod;
hash[i][id(s[i-1])]=(hash[i][id(s[i-1])]+1)%mod;
}
while(t--){
cin>>k>>l1>>r1>>l2>>r2;
for(i=1;i<=26;i++) fa[i]=i;
for(i=1;i<=k;i++){
char a,b;
cin>>a>>b;
if(find(id(a))!=find(id(b))) fa[find(id(a))]=find(id(b));
}
bool flag=1;
while(l1<r1&&l2<r2){
int mid1=(l1+r1)/2,mid2=(l2+r2)/2;
int h1=Hash(l1,mid1),h2=Hash(mid1+1,r1),h3=Hash(l2,mid2),h4=Hash(mid2+1,r2);
if(h1!=h3&&h2!=h4){
flag=0;
break;
}
else if(h1==h3&&h2==h4) break;
else if(h1==h3) l1=mid1+1,l2=mid2+1;
else r1=mid1,r2=mid2;
}
if(flag) puts("YES");
else puts("NO");
}
return 0;
}