1. 程式人生 > >KMP演算法——South Central USA 2006 藍色牛仔褲

KMP演算法——South Central USA 2006 藍色牛仔褲

nkoj 1479

Description

IBM和“國家地理”雜誌共同研究的一個名為“藍色牛仔褲”的專案,就是分析成千上萬個捐贈的DNA,以便找出世界的人口是怎樣構成和分佈的。 
作為IBM的一名研究員,你的任務就是寫一個程式來研究不同DNA片段間的聯絡。 
一個DNA序列由A、T、G、C四個字母來表示,比如“TAGACC”是一種長度為6的DNA序列。 

告訴你若干條DNA序列,請找出最長的一段連續DNA序列,該序列出現在了給出的所有DNA序列中(注:也就是求最長公共子串)。

Input

第一行,一個整數n,表示下面有n組測試資料n<=20 
對於每組測試資料: 
第一行,一個整數m (2 <= m <= 10),表示告訴你了m條DNA序列。 
接下來m行,每行表示一條DNA序列,每行的長度不超過60 

Output

對於每組測試資料,輸出它的最長公共子串。 
如果子串的長度小於3,輸出"no significant commonalities" 
如果有多條長度相等的最長子串,輸出字典序最小的那條。

Sample Input

3
2
GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
3
GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA
GATACTAGATACTAGATACTAGATACTAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA
GATACCAGATACCAGATACCAGATACCAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA
3
CATCATCATCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
ACATCATCATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AACATCATCATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT

Sample Output

no significant commonalities
AGATAC
CATCATCAT
分析:
主要問題是找出n個字串的最長公共子串,由於資料規模較小,暴力即可:
找出最短的字串,列舉其所有子串,檢查是否也是其他字串的子串。長度由大到小列舉,第一個滿足題意的長度中選擇字典序最小的輸出即可。
檢查子串用KMP演算法實現
程式碼如下:
#include<iostream>
#include<cstring>
using namespace std;
int n,f[70];  //f為kmp演算法中的回退表 
string s[15];
void get_fail(int x){  //預處理s[x]的回退表 
	int i,j=-1,len=s[x].length();
	memset(f,0,sizeof(f));
	f[0]=j=-1;   //注意i,j,f[0]的初值 
	for(i=1;i<len;i++){
		while(j>=0&&s[x][i]!=s[x][j+1])j=f[j] ;
		if(s[x][j+1]==s[x][i])j++;
		f[i]=j;
	}
}
bool check(int begin,int len,int x,int y){
	  //檢查s[x] begin開始,長度為len的子串是否為 y的子串 
	int i,j=-1,l=s[y].length();  //注意j=-1 ,i=0; 
	for(i=0;i<l;i++){    
		while(j>=0&&s[x][begin+j+1]!=s[y][i])j=f[j];
		if(s[x][begin+j+1]==s[y][i])j++;
		if(j==len-1)return true;  //找到了 
	}
	return false;
}
void solve(int x){
	int len=s[x].length(),i,j,k;
	int find_sub=0;
	string ans;
	get_fail(x);   
	for(i=len;i>=3;i--){  //列舉子串的長度 
		for(j=0;j<=len-i;j++){   //列舉子串的起點 
			bool ok=true;
			for(k=1;k<=n;k++)   //依次檢查各個字串 
				if(k!=x&&!check(j,i,x,k)){   
					ok=false;break;
				}
			if(ok){  //找到字典序最小的; 
				find_sub++;
				if(find_sub==1)ans=s[x].substr(j,i);
				else ans=min(ans,s[x].substr(j,i));
			}
		}
		if(find_sub){cout<<ans<<endl;return;}
	}
	cout<<"no significant commonalities"<<endl;
}
int main(){
	ios_base::sync_with_stdio(false);
	int i,t,l;
	cin>>t;
	while(t--){
		int minn=2e9,x;
		cin>>n;
		for(i=1;i<=n;i++) {   //找出最短的字串 
			cin>>s[i];
			l=s[i].length();
			if(l<minn)minn=l,x=i;
		}
		solve(x); 
	}
}