1. 程式人生 > 實用技巧 >【POJ 2778】DNA Sequence

【POJ 2778】DNA Sequence

題目

題目連結:http://poj.org/problem?id=2778
給出 \(n\) 個長度不超過 \(10\) 且僅由 \(\operatorname{A,C,T,G}\) 組成的串 \(s_i\),問有多少個長度為 \(m\) 的僅由 \(\operatorname{A,C,T,G}\) 串不存在任意一個子串為 \(s\)
\(n\leq 10,m\leq 2\times 10^9\)

吐槽

從星期五開始寫,差不多 \(40min\) 過了樣例,但是交到 POJ 上一直 WA。
週六發現是矩陣乘法中兩個元素相乘可能超過 int 的範圍,改了之後一直 TLE。
週日早上心態大崩開始和標程對拍,發現完全沒有問題。一度懷疑是 POJ 評測環境的問題。但是標程本地需要 \(70ms\)

,我的程式碼本地 \(300ms\)。感覺不至於在 POJ 上能慢個 \(700ms\) 吧所以就沒管。
晚上優化了矩陣乘法的常數,程式碼降到了 \(70ms\),終於在 POJ 上過了。跑了 \(500+ms\)。。。
POJnb。

思路

首先建立 AC 自動機,定義一個點 \(x\)\(end[x]=1\) 當且僅當滿足以下條件之一:

  • 存在一個串在點 \(x\) 結束。
  • 存在一個串在點 \(x\) 的祖先結束
  • \(end[fail[x]]=1\)

那麼如果 \(end[x]=1\),那麼最終的串就不能存在一個子串等於根到 \(x\) 所構成的字串。
那麼可以把加字母的過程看做在 AC 自動機上一個點在移動,假設要加入一個字母 \(a\)

,那麼這個點 \(p\) 就移動到 \(c[p][a]\) 的位置。
那麼一個串是合法的當且僅當這一個點沒有經過 \(end=1\) 的點。
由於 AC 自動機上的點不超過 \(n\times |S|\leq 100\) 個,可以用矩陣乘法優化。
時間複雜度 \(O(n^3|S|^3\log m)\)

程式碼

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N=110,M=5,MOD=100000;
int n,m,Q,ans;
char s[N];

inline int ID(char ch)
{
	if (ch=='A') return 1;
	if (ch=='C') return 2;
	if (ch=='T') return 3;
	return 4;
}

struct Matrix
{
	int a[N][N];
	Matrix() { memset(a,0,sizeof(a)); }
}mat;

struct ACA
{
	int tot,c[N][M],fail[N];
	bool end[N];
	
	inline void ins(char *s)
	{
		int len=strlen(s),p=0;
		for (int i=0;i<len;i++)
		{
			int id=ID(s[i]);
			if (!c[p][id]) c[p][id]=++tot;
			p=c[p][id];
		}
		end[p]=1;
	}
	
	inline void build()
	{
		queue<int> q;
		for (int i=1;i<=4;i++)
			if (c[0][i]) q.push(c[0][i]);
		while (q.size())
		{
			int u=q.front(); q.pop();
			if (end[fail[u]]) end[u]=1;
			for (int i=1;i<=4;i++)
				if (c[u][i])
				{
					fail[c[u][i]]=c[fail[u]][i];
					q.push(c[u][i]);
					if (end[u]) end[c[u][i]]=1;
				}
				else c[u][i]=c[fail[u]][i];
		}
	}
	
	inline void updmat()
	{
		for (int i=0;i<=tot;i++)
			for (int j=1;j<=4;j++)
				if (!end[i] && !end[c[i][j]])
					mat.a[i][c[i][j]]++;
	}
}AC;

Matrix operator *(Matrix a,Matrix b)
{	
	Matrix c;
	for (int i=0;i<=AC.tot;i++)
		for (int j=0;j<=AC.tot;j++)
			for (int k=0;k<=AC.tot;k++)  //這個地方 AC.tot 寫成 N 就會 T。。。
				c.a[i][j]=(c.a[i][j]+1LL*a.a[i][k]*b.a[k][j])%MOD;
	return c;
}
	
inline Matrix fpow(Matrix a,int k)
{
	Matrix b;
	b.a[0][0]=1;
	for (;k;k>>=1,a=a*a)
		if (k&1) b=b*a;
	return b;
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%s",s);
		AC.ins(s);
	}
	AC.build(); AC.updmat();
	mat=fpow(mat,m);
	for (int i=0;i<=AC.tot;i++)
		ans=(ans+mat.a[0][i])%MOD;
	printf("%d\n",ans);
	return 0;
}