1. 程式人生 > >USACO最長字首(trie練習題)

USACO最長字首(trie練習題)

點我

題目描述

在生物學中,一些生物的結構是用包含其要素的大寫字母序列來表示的。生物學家對於把長的序列分解成較短的序列(即元素)很感興趣。

如果一個集合 P 中的元素可以通過串聯(元素可以重複使用,相當於 Pascal 中的 “+” 運算子)組成一個序列 S ,那麼我們認為序列 S 可以分解為 P 中的元素。元素不一定要全部出現(如下例中BBC就沒有出現)。舉個例子,序列 ABABACABAAB 可以分解為下面集合中的元素:

{A, AB, BA, CA, BBC}

序列 S 的前面 K 個字元稱作 S 中長度為 K 的字首。設計一個程式,輸入一個元素集合以及一個大寫字母序列 S ,設S'是序列S的最長字首,使其可以分解為給出的集合P中的元素,求S'的長度K。

輸入輸出格式

輸入格式:

輸入資料的開頭包括 1..200 個元素(長度為 1..10 )組成的集合,用連續的以空格分開的字串表示。字母全部是大寫,資料可能不止一行。元素集合結束的標誌是一個只包含一個 “.” 的行。集合中的元素沒有重複。接著是大寫字母序列 S ,長度為 1..200,000 ,用一行或者多行的字串來表示,每行不超過 76 個字元。換行符並不是序列 S 的一部分。

輸出格式:

只有一行,輸出一個整數,表示 S 符合條件的字首的最大長度。

輸入輸出樣例

輸入樣例#1:
A AB BA CA BBC
.
ABABACABAABC
輸出樣例#1:
11

說明

翻譯來自NOCOW

USACO 2.3

程式碼

#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<ctime>
#define For(i,a,b) for(register int i=a;i<=b;++i)
#define Rep(i,a,b) for(register int i=a;i>=b;--i)
const int maxx=1e5+7;
using namespace std;
int be[2000001],ne[2000001],to[2000001],e=0,cnt=0;
void add(int x, int y){			//鏈式前向星存trie
    to[++e]=y;
    ne[e]=be[x];
    be[x]=e;
}
struct node{
    bool end; char x;
}llz[2000001];
bool f[2000001];
char x[1200],s[2000010];
void insert(char *s){				//trie的插入
    int i,u=0,len=strlen(s);
    For(v,0,len-1){
        bool flag=0;
        for(i=be[u];i;i=ne[i]){
            int go=to[i];
            if(llz[go].x==s[v]){
                u=go;
                flag=1;
                break;
            }
        }
        if(!flag) {add(u,++cnt); u=cnt; llz[u].x=s[v];}		//
        if(v==len-1) llz[u].end=1;				//標記結束位置
    }
}
bool find(int l,int r){						//trie的查詢
    int i,u=0,flag;
    For(v,l,r){
        flag=0;
        for(i=be[u];i;i=ne[i]){
            int go=to[i];
            if(llz[go].x==s[v]){
                u=go;
                flag=1;
                break;
            }
        }
        if(!flag)return 0;
        if(v==r)
            if(llz[u].end==1){
                return 1;
            }
            else return 0;
    }
}
int main(){
#ifndef ONLINE_JUDGE
    freopen("input.in", "r", stdin);
    freopen("output.out", "w", stdout);
#endif
    int i,j,l,ans=0,M=0;
    while(1){						//對每個單詞集合建trie
        scanf("%s",x);
        if(x[0]=='.') break;
		i=strlen(x);
		M=max(i,M);					//找到最長的長度,下面會有用的
        insert(x);
    }
    scanf("%s",s+1);
    while(scanf("%s",x)!=EOF) strcat(s+1,x);		//讀入s串
    l=strlen(s+1);
    f[0]=1;							//f[x]表示在x位置之前所有的字母能匹配
    For(i,1,l){
        for(j=i-1;j>=0;j--)
            if(f[j] && find(j+1,i)){	//如果j以前能全部匹配,並且j+1到i能匹配,i位置也能全匹配
                f[i]=1;
                ans=i;
                break;
            }
		if(j<0 && i-ans>M) break;    //剪枝,如果當前比最長集合還要長的位置沒匹配成功,後面也不能匹配上
        }
    printf("%d\n",ans);
    return 0;
}


code by  羅旅洲