DNA
加里敦大学的生物研究所,发现了决定人喜不喜欢吃藕的基因序列 \(S\),有这个序列的碱基序列就会表现出喜欢吃藕的性状,但是研究人员发现对碱基序列 \(S\),任意修改其中不超过 \(3\) 个碱基,依然能够表现出吃藕的性状。现在研究人员想知道这个基因在 DNA 链 \(S_0\) 上的位置。所以你需要统计在一个表现出吃藕性状的人的 DNA 序列 \(S_0\) 上,有多少个连续子串可能是该基因,即有多少个 \(S_0\) 的连续子串修改小于等于三个字母能够变成 \(S\)。
对于 \(100\%\) 的数据,\(S_0,S\) 的长度不超过 \(10^5\),\(0\lt T\leq 10\)。
分析
https://106960.blog.luogu.org/solution-p3763
随便翻的时候看到了这道题。hash好题,枚举每个位置判断一下能否在三次以内匹配就行了。
时间复杂度\(O(n\log m)\)
co int N=1e5+1,D=131;
char a[N],b[N];
int n,m;
ull p[N],f[N],g[N];
ull ask(ull*f,int l,int r){
return f[r]-f[l-1]*p[r-l+1];
}
int lcp(int x,int y,int r){
int l=0;
while(l>1;
if(ask(f,x,x+mid-1)==ask(g,y,y+mid-1)) l=mid;
else r=mid-1;
}
return l;
}
bool check(int x){
int r=x+m-1,y=1;
for(int i=0;i<3;++i){
int t=lcp(x,y,m-y+1);
x+=t+1,y+=t+1;
if(y>m) return 1;
}
return ask(f,x,r)==ask(g,y,m);
}
void DNA(){
scanf("%s",a+1),n=strlen(a+1);
scanf("%s",b+1),m=strlen(b+1);
if(n();t--;) DNA();
return 0;
}
隐身术
给定两个串\(A\)、\(B\)。请问\(~B~\)中有多少个非空子串和\(~A~\)的编辑距离不超过\(K\)?
所谓“子串”,指的是\(~B~\)中连续的一段。不同位置的内容相同的子串算作多个。两个串之间的“编辑距离”指的是把一个串变成另一个串需要的最小的操作次数,每次操作可以插入、删除或者替换一个字符。
对\(100\%\)的数据,\(K\leq 5\),两个字符串均非空,长度和小于\(10^5\)。
题解
http://jklover.hs-blog.cf/2020/05/27/bzoj-4340-隐身术/#more
SAM + dfs 爆搜.
枚举 B 的子串左端点 \(t\) ,即考虑所有是以 \(t\) 开头的后缀的前缀.
\(k\) 较小,可爆搜之,只要保证每次 \(k\) 都在减少, dfs 的层数就是 \(O(k)\) 的.
具体地,设状态 \((x,y,z)\) 表示串 \(A\) 匹配到了位置 \(x\) , 串 \(B\) 匹配到了位置 \(y\) ,还剩 \(z\) 次修改机会.
若 \(A_x=B_y\) ,则我们需要先跳过一段连续的可以匹配的段,这样接下来就一定会消耗一次修改机会.
在 SAM 上询问这两个位置的 LCP ,将其跳过即可.
若 \(A_x\neq B_y\) ,则必须使用修改操作,可以将 \(B_y\) 删掉,或在 \(B\) 中加入一个 \(A_x\) ,或将 \(B_y\) 改成 \(A_x\) .
这三种操作分别转移到 \((x,y+1,z-1),(x+1,y,z-1),(x+1,y+1,z-1)\) .
当某一个串匹配完成时,由于可能还剩下了若干修改操作,合法的前缀是一段区间,差分打上标记即可.
时间复杂度 \(O(n\log n+n\cdot k^3)\) .
CO int N=2e5+10;
char A[N],B[N];
int last=1,tot=1;
array ch[N];
int fa[N],len[N],idx[N];
void extend(int c,int p){
int x=last,cur=last=++tot;
len[cur]=len[x]+1,idx[p]=cur;
for(;x and !ch[x][c];x=fa[x]) ch[x][c]=cur;
if(!x) {fa[cur]=1; return;}
int y=ch[x][c];
if(len[y]==len[x]+1) {fa[cur]=y; return;}
int clone=++tot;
ch[clone]=ch[y],fa[clone]=fa[y],len[clone]=len[x]+1;
fa[cur]=fa[y]=clone;
for(;ch[x][c]==y;x=fa[x]) ch[x][c]=clone;
}
vector to[N];
int pos[N],num,st[2*N][19],lg[2*N];
void dfs(int x){
pos[x]=++num,st[num][0]=len[x];
for(int y:to[x]) dfs(y),st[++num][0]=len[x];
}
int diff[N];
int main(){
int K=read();
scanf("%s%s",A+1,B+1);
int n=strlen(A+1),m=strlen(B+1);
for(int i=m;i>=1;--i) extend(B[i]-'A',m-i+1);
extend(26,m+1);
for(int i=n;i>=1;--i) extend(A[i]-'A',n-i+1+m+1);
for(int i=2;i<=tot;++i) to[fa[i]].push_back(i);
dfs(1);
lg[0]=-1;
for(int i=1;i<=num;++i) lg[i]=lg[i>>1]+1;
for(int i=1;i<=lg[num];++i)for(int j=1;j+(1< query=[&](int i,int j)->int{
if(i>n or j>m) return 0;
int x=pos[idx[n-i+1+m+1]],y=pos[idx[m-j+1]];
if(x>y) swap(x,y);
int k=lg[y-x+1];
return min(st[x][k],st[y-(1< dfs=[&](int x,int y,int z)->void{
int lcp=query(x,y);
x+=lcp,y+=lcp;
if(x>n or y>m){
int d=z-(n-x+1); // extra operations
if(d<0) return;
int l=max(y-t-d,1),r=min(y-t+d,m-t+1); // valid length
++diff[l],--diff[r+1];
return;
}
else if(z){
dfs(x+1,y,z-1);
dfs(x,y+1,z-1);
dfs(x+1,y+1,z-1);
}
};
dfs(1,t,K);
for(int i=L;i<=R;++i) diff[i]+=diff[i-1];
for(int i=L;i<=R;++i)if(diff[i]) ++ans,diff[i]=0;
}
printf("%d\n",ans);
return 0;
}