[USACO17OPEN]Bovine Genomics G
二分+hash前缀和+set
二分是很显然的,那么 check 的过程是一个给字符串判等的过程,那么自然就会想到 hash :
(也就是把字符串看成一个 base 进制数)
(一般会加一个模数,但实际上放着不管就相当于对 unsigned long long 的最大值取模了,效率更高风险更大,至少在这题没出什么锅)
这样的 hash ,用类似前缀和的思想存下到每一位的 hash 值之后,就可以做到 查询子串的 hash 值:
比如 时:
(当然 base 要预处理 i 次幂)
二分答案长度,枚举每一个左端点,将前 n 个串的 hash 值存到 set 里,再遍历后 n 个字符串,如果某个字符串的 hash 值已在 set 里,说明有重复
一些让我luogu最优解第二的卡常小技巧:
- 读入字符串时,实测使用
cin.tie(0),ios::sync_with_stdio(0);
的cin比快读要快
- unordered_set 是无序的听起来会快很多,但也因此迭代的效率不如普通set,在此题中也是set稍快一点点
完整代码qwq:
#include<bits/stdc++.h>
#define EL puts("Elaina")
#define reg register int
#define qwq 0
using namespace std;
typedef unsigned long long ull;
const int maxn=5e2+3;
const ull base=5;
ull pw[maxn];
int val[137];
inline void init(){
pw[0]=1,val['A']=1,val['C']=2,val['G']=3,val['T']=4;
for(reg i=1;i<maxn;++i)
pw[i]=pw[i-1]*base;
}
int n,m;
ull h[2][maxn][maxn];
char s[maxn];
inline void getHash(int p,int x){
for(reg i=1;i<=m;++i)
h[p][x][i]=h[p][x][i-1]*base+val[s[i]];
}
inline ull zStr(int p,int x,int l,int r){
return h[p][x][r]-h[p][x][l-1]*pw[r-l+1];
}
inline bool check(int l,int r){
set <ull> S;
for(reg i=1;i<=n;++i){
ull tmp=zStr(0,i,l,r);
S.insert(tmp);
}
for(reg i=1;i<=n;++i){
ull tmp=zStr(1,i,l,r);
if(S.count(tmp))return 1;
}
return 0;
}
inline bool check(int len){
for(reg l=1,r=len;r<=m;++l,++r)
if(!check(l,r))return 1;
return 0;
}
void solve(){
cin>>n>>m;
for(reg i=1;i<=n;++i)
cin>>s+1,getHash(0,i);
for(reg i=1;i<=n;++i)
cin>>s+1,getHash(1,i);
int l=0,r=m,mid,ans;
while(l<=r){
mid=(l+r)>>1;
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d\n",ans);
}
int main(){
cin.tie(qwq),ios::sync_with_stdio(qwq);
init();
solve();
return qwq;
}