题目

【SAM】【GDKOI2014】基因模式_题组

思路

SA
首先将T和Si首尾相接连起来,每两个串之间隔一个未出现过的字符,求出后缀数组及height数组,即后缀数组中相邻两个后缀的最长公共前缀。然后O(N)扫描可得每个后缀i和在原T串中开始的每个后缀j最长的最长公共前缀是多少,记为near[i],这样后缀j即在后缀数组中与后缀i最近的两个在T中开始的后缀中的一个。即。这样我们就可以求出在Sp中每个位置Sp,q向后匹配最长near[i]长度能使在这段长度内任意的以Sp,q开始的串都能在T中找到对应的子串。
现在我们已经满足了在T串中有相应子串的条件,还需要满足一定的奇偶性。用一个四位的二进制表示四种碱基的奇偶性。对于每个Si,我们做如下统计:用一个0~15的数组f表示从st到en区间内每种奇偶性出现了多少次。从前向后扫描,移动st和en使对于每个i有st=i及en=i+near[i]-1,注意到st和en只会向后移动,对于每个i可用16的时间扫面数组统计满足要求的。en=en+1时,是将原来统计到的所有以en-1结尾的串转变为以en结尾,每个被统计到的串都在尾部加上了碱基en,记是从数组f转移到f’, f’[i]=f[ix],表示异或,x表示碱基en本身奇偶性,即x是一个二的幂。此外还要f’[x]=f’[x]+1,即添加了一个仅由en构成的串。st=st+1时,删除了一个从st一直到en的最长的串,直接在对应位置减一即可。
综上,我们用O(Nlog2N)的时间构建后缀数组,再用O(16N)的时间扫描。你也可以用DC3把后缀数组写成O(N)的。这里的N指的是所有字符串的总长度。
SAM
可是出题人说本来不想让上面的算法过的,应该用后缀自动机。对T建后缀自动机,然后用每个Si在后缀自动机上匹配,即可轻松地求出near。后面的扫描同上。这个算法把构建后缀数组的O(Nlog2N)优化为O(4N),而且这里的N不再是所有字符串的总长度而是T的长度,速度会略有提升。

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+77,S=1<<4;
int n,m,q;
char s[N],t[N],id[128],yjy[4];
namespace SAM
{
	struct node
	{
		int ch[4];
		int fa,len;
	} sam[(N << 1)+5];
	int las=1,tot=1;
	inline void ins(int x)
	{
		int cur=las,p=++tot;
		sam[p].len=sam[cur].len+1;
		for(;cur && !sam[cur].ch[x];cur=sam[cur].fa)
			sam[cur].ch[x]=p;
		if(!cur)
			sam[p].fa=1;
		else
		{
			int q=sam[cur].ch[x];
			if(sam[cur].len+1 == sam[q].len)
				sam[p].fa=q;
			else
			{
				int nxt=++tot;
				sam[nxt]=sam[q],sam[nxt].len=sam[cur].len+1,sam[p].fa=sam[q].fa=nxt;
				for(;cur && sam[cur].ch[x] == q;cur=sam[cur].fa)
					sam[cur].ch[x]=nxt;
			}
		}
		las=p;
	}
}
int l,cnt[S+5],cur,tag;
ll ans;
int main()
{
	id['C']=id['c']=1,id['G']=id['g']=2,id['T']=id['t']=3;
	scanf("%d%s",&q,s+1),n=strlen(s+1);
	for(int i=1;i<=n;++i)
		SAM::ins(id[s[i]]);
	for(;q;--q)
	{
		l=1,cur=tag=ans=0,memset(cnt,0,sizeof cnt);
		scanf("%s%s",s+1,t),m=strlen(s+1),memset(yjy,-1,sizeof yjy);
		for(char *c=t;*c;yjy[id[*c]]=*c >= 'A' && *c<='Z',++c);
		for(int i=1,x,p=1,len=0;i<=m;++i)
		{
			x=id[s[i]];
			if(SAM::sam[p].ch[x])	
				p=SAM::sam[p].ch[x],++len;
			else
			{
				for(;p && !SAM::sam[p].ch[x];p=SAM::sam[p].fa);
				!p ? (p=1,len=0) : (len=SAM::sam[p].len+1,p=SAM::sam[p].ch[x]);
			}
			for(;l < i - len+1;++l)
				--cnt[cur ^ tag],cur ^= 1 << id[s[l]];
			tag ^= 1 << x,cur ^= 1 << x,++cnt[tag ^ (1 << x)];
			for(int i0=0;i0 < 2;++i0)
				if(yjy[0] == -1 || yjy[0] == i0)
					for(int i1=0;i1 < 2;++i1)
						if(yjy[1] == -1 || yjy[1] == i1)
							for(int i2=0;i2 < 2;++i2)
								if(yjy[2] == -1 || yjy[2] == i2)
									for(int i3=0;i3 < 2;++i3)
										if(yjy[3] == -1 || yjy[3] == i3)
											ans += cnt[(i0 | (i1 << 1) | (i2 << 2) | (i3 << 3)) ^ tag];
		}
		printf("%lld\n",ans);
	}
}