请先学会必备的“前缀和”、“哈希字符串”、“二分法”;
图片来自在线oj平台CSG-CPC:1243:相似基因序列问题 (csgrandeur.cn)
思路:
因为N,Q最大为300,M最大为60000,所以暴力循环最多为300*300*60000=54亿,即使是2.5秒也远远不够,所以至少要优化成O(N*Q*log2(M))的复杂度。一看到log我们应该就可以把二分法当作备选方案,而二分的必要条件是满足单调性,于是有以下:
对于两个字符串S1=“aabbccdd”,和S2=“aabgccff”,我们观察字符串,S1和S2不相等,但S1的前三个字符和S2的前三个字符是一样的,从第四个字符往后,越往后不一样的字符个数可能就越多。由此我们或许可以注意到两个字符串由短到长,不一样字符的个数是符合单调性的(单调增),也就是越长就可能错的越多。那么自然,当两个字符串不相等的时候,我们想要知道有几个不相等的字符就可以通过二分法找第一个不相等的地方,再判断这个字符之后的字符串是否相等。再一直重复以上过程。
但新的问题来了,怎么在O(1)下判断两个字符串相等?很显然,可以用哈希字符串,把字符串转化为哈希值,并用数组存它们的哈希值前缀和,想要判断两个字符串在L到R这一段是否相等,只需要用下面get_hash(乱起的名)函数就可以判断了。
所以代码:
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define ull unsigned long long
int n,q,m,k,P=131;
ull nha[301][60001];// 进化树上的n个字符串的前缀哈希数组
ull qha[301][60001];// 待查询的q个字符串的前缀哈希数组
ull Pi[60001]; //P的i次方数组
bool get_hash(int a,int b,int L,int R){
//返回进化树上第a个字符串和待查询的第b个字符串的L到R段是否相等
return nha[a][R]-nha[a][L-1]*Pi[R-L+1]==qha[b][R]-qha[b][L-1]*Pi[R-L+1];
}
int main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>q>>m>>k;
char c;
Pi[0]=1;
//预处理P的i次方
for(int i=1;i<=m;i++){
Pi[i]=Pi[i-1]*P;
}
//转化为哈希前缀和
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>c;
nha[i][j]=nha[i][j-1]*P+c;
}
}
for(int i=1;i<=q;i++){
for(int j=1;j<=m;j++){
cin>>c;
qha[i][j]=qha[i][j-1]*P+c;
}
//每输入完一个待询问字符串 就可以算它的答案
int ans=0;
for(int t=1;t<=n;t++){
int cur=1;
int cnt=0;
while(cur<=m&&!get_hash(t,i,cur,m)){
int l=cur,r=m;
while(l<=r){
int mid=(l+r)>>1;
if(get_hash(t,i,cur,mid)){
l=mid+1;
cur=mid;
}else{
r=mid-1;
}
}
if(cur<=m){
cnt++;
cur=l+1;
}
if(cnt>k){
break;
}
}
if(cnt<=k){
ans++;
}
}
cout<<ans<<"\n";
}
return 0;
}
CSG-CPC给出的样例:
6 4 4 1
kaki
kika
manu
nana
tepu
tero
kaka
mana
teri
anan
输出:
2
2
1
0
8 6 7 3
delphis
aduncus
peronii
plumbea
clymene
hectori
griseus
electra
delphis
helpiii
perphii
clumeee
eleelea
ddlpcus
输出:
1
1
2
2
1
2