TJOI2017 DNA 和 BJOI2015 隐身术

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<r){
		int mid=l+r+1>>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<m) return puts("0"),void();
	for(int i=1;i<=n;++i) f[i]=f[i-1]*D+a[i];
	for(int i=1;i<=m;++i) g[i]=g[i-1]*D+b[i];
	int ans=0;
	for(int i=1;i+m-1<=n;++i)if(check(i)) ++ans;
	printf("%d\n",ans);
}
int main(){
	p[0]=1;for(int i=1;i<N;++i) p[i]=p[i-1]*D;
	for(int t=read<int>();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<int,27> 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<int> 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<int>();
	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<<i)-1<=num;++j)
		st[j][i]=min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
	function<int(int,int)> 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<<k)+1][k]);
	};
	int ans=0;
	int L=max(n-K,1),R=min(n+K,m);
	for(int t=1;t<=m;++t){
		function<void(int,int,int)> 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;
}

posted on 2020-06-03 20:59  autoint  阅读(147)  评论(0编辑  收藏  举报

导航