hdu 3341 Lost's revenge(dp+Ac自动机)

来源:互联网 发布:sql server insert语句 编辑:程序博客网 时间:2024/06/06 03:18

题目:http://acm.hdu.edu.cn/showproblem.php?pid=3341

Lost's revenge

Time Limit: 15000/5000 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others)
Total Submission(s): 3369    Accepted Submission(s): 896


Problem Description
Lost and AekdyCoin are friends. They always play "number game"(A boring game based on number theory) together. We all know that AekdyCoin is the man called "nuclear weapon of FZU,descendant of Jingrun", because of his talent in the field of number theory. So Lost had never won the game. He was so ashamed and angry, but he didn't know how to improve his level of number theory.

One noon, when Lost was lying on the bed, the Spring Brother poster on the wall(Lost is a believer of Spring Brother) said hello to him! Spring Brother said, "I'm Spring Brother, and I saw AekdyCoin shames you again and again. I can't bear my believers were being bullied. Now, I give you a chance to rearrange your gene sequences to defeat AekdyCoin!".

It's soooo crazy and unbelievable to rearrange the gene sequences, but Lost has no choice. He knows some genes called "number theory gene" will affect one "level of number theory". And two of the same kind of gene in different position in the gene sequences will affect two "level of number theory", even though they overlap each other. There is nothing but revenge in his mind. So he needs you help to calculate the most "level of number theory" after rearrangement.
 

Input
There are less than 30 testcases.
For each testcase, first line is number of "number theory gene" N(1<=N<=50). N=0 denotes the end of the input file.
Next N lines means the "number theory gene", and the length of every "number theory gene" is no more than 10.
The last line is Lost's gene sequences, its length is also less or equal 40.
All genes and gene sequences are only contains capital letter ACGT.
 

Output
For each testcase, output the case number(start with 1) and the most "level of number theory" with format like the sample output.
 

Sample Input
3ACCGGTCGAT1AAAAA0
 

Sample Output
Case 1: 3Case 2: 2
做这题真的是闹心,只怪自己开始没有估计好时间复杂度瞎折腾,走了不少弯路。第一次想用Ac自动机检验长串的各种排列取最大值即可,但是长串有可能存在字符想通过的情况,排列一定小于length!,怎样才能把所有的非重复的字符串排列找出来呢?《C语言名题精选百则》里有个旋转法,但是我研究了半天也没有把重复的去掉。后来发现next_permutation是个好东西,可以除去重复的串,但前提是初始串是单调的,这简单,我用一个sort不就可以了吗?开心啊。。
TLE:
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;int ans;char word[15],mystring[50];struct node{    int count;    node *next[4],*fail;    node(){        count=0;        fail=NULL;        memset(next,0,sizeof(next));    }};node *root;int getdex(char a){    int ans;    if(a=='A')ans=0;    else if(a=='C') ans=1;    else if(a=='G') ans=2;    else ans=3;    return ans;}void insert(char *s){    int length=strlen(s);    node *p=root;    for(int i=0;i<length;i++){        int dex=getdex(s[i]);        if(p->next[dex]==0)p->next[dex]=new node();        p=p->next[dex];    }    p->count++;}node *que[1000];void build_ac(){    int head,tail;    head=tail=0;    que[tail++]=root;    while(head<tail){        node *p=que[head++],*temp=NULL;        for(int i=0;i<4;i++){            if(p->next[i]){                if(p==root)p->next[i]->fail=root;                else {                temp=p->fail;                while(temp){                    if(temp->next[i]){                        p->next[i]->fail=temp->next[i];                        break;                    }                    temp=temp->fail;                }                if(!temp)p->next[i]->fail=root;                }                que[tail++]=p->next[i];            }        }    }}void del(node *p){    if(p==NULL)return ;    for(int i=0;i<4;i++)del(p->next[i]);    delete p;}int query(int str[],int str_len){    int i,dex,result=0;    node *p=root;    for(i=0;i<str_len;i++){        while(p->next[str[i]]==NULL&&p!=root)p=p->fail;        p=p->next[str[i]];        if(!p)p=root;        node *temp=p;        while(temp!=root){ //&&temp->count!=-1            result+=temp->count;            //temp->count=-1;            temp=temp->fail;        }    }    return result;}void show(int str[],int len){    for(int i=0;i<len;i++)cout<<str[i]<<" ";  cout<<endl;}int main(){    //freopen("cin.txt","r",stdin);    int n,ca=1;    while(cin>>n&&n){        root=new node();        while(n--){            scanf("%s",word);            insert(word);        }        build_ac();        scanf("%s",mystring);        int str_len=strlen(mystring);        int str[50];        for(int i=0;i<str_len;i++){            str[i]=getdex(mystring[i]);        }        int ans=0;        sort(str,str+str_len);        do{            ans=max(ans,query(str,str_len));        }while(next_permutation(str,str+str_len));        printf("Case %d: %d\n",ca++,ans);        del(root);    }    return 0;}
郁闷啊,TLE了,是不是指针太耗时间了,于是我又把指针改成数组:
TLE:
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <queue>using namespace std;int ans,top;char word[15],mystring[50];struct node{    int next[4],fail;    int count;    node(){        count=0;        fail=NULL;        memset(next,0,sizeof(next));    }}tree[510];int getdex(char a){    int ans;    if(a=='A')ans=0;    else if(a=='C') ans=1;    else if(a=='G') ans=2;    else ans=3;    return ans;}void insert(char *s){    int length=strlen(s),p=0;    for(int i=0;i<length;i++){        int dex=getdex(s[i]);        if(tree[p].next[dex]==0){            top++;            tree[p].next[dex]=top;        }        p=tree[p].next[dex];    }    tree[p].count++;}void build_ac(){    queue<int> q;    q.push(0);    int i,p,cur,son;    while(!q.empty()){        p=q.front();        q.pop();        for(int i=0;i<4;i++){            if(tree[p].next[i]){                son=tree[p].next[i];                if(p==0)tree[son].fail=p;                else {                    cur=tree[p].fail;                    while(cur&&tree[cur].next[i]==0)cur=tree[cur].fail;                    tree[son].fail=tree[cur].next[i];                }                if(tree[tree[son].fail].count)                    tree[son].count+=tree[tree[son].fail].count;                q.push(son);            }            else   tree[p].next[i]=tree[tree[p].fail].next[i];        }    }}int query(int str[],int str_len){    int i,dex,result=0;    int p=0;    for(i=0;i<str_len;i++){        while(tree[p].next[str[i]]==NULL&&p!=0)p=tree[p].fail;        p=tree[p].next[str[i]];  //p=p->next[str[i]];        if(!p)p=0;        int temp=p;        while(temp!=0){ //&&temp->count!=-1            result+=tree[temp].count;            //temp->count=-1;            temp=tree[temp].fail;        }    }    return result;}void show(int str[],int len){    for(int i=0;i<len;i++)cout<<str[i]<<" ";  cout<<endl;}int main(){    //freopen("cin.txt","r",stdin);    int n,ca=1;    while(cin>>n&&n){        //tree[0].node();        while(n--){            scanf("%s",word);            insert(word);        }        build_ac();        scanf("%s",mystring);        int str_len=strlen(mystring);        int str[50];        for(int i=0;i<str_len;i++){            str[i]=getdex(mystring[i]);        }        int ans=0;        sort(str,str+str_len);        do{            ans=max(ans,query(str,str_len));        }while(next_permutation(str,str+str_len));        printf("Case %d: %d\n",ca++,ans);        memset(tree,0,sizeof(tree));    }    return 0;}
还是TLE!能不能愉快的玩耍了?仔细分析:原来40长度4种最小单位的长串排列估算有:40!/10!/10!/10!/10!=4.71e21,哎,一开始怎么不想想这个问题,还做了半天无用功!接下来怎么办呢?看了大神们的思路,DP啊,空间换时间。组合情况:11*11*11*11<15000。真是巧妙啊。。
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <queue>using namespace std;int ans,top;char word[15],mystring[50];struct node{    int next[4],fail;    int count;}tree[510];int getdex(char a){    int ans;    if(a=='A')ans=0;    else if(a=='C') ans=1;    else if(a=='G') ans=2;    else ans=3;    return ans;}void insert(char *s){    int length=strlen(s),p=0;    for(int i=0;i<length;i++){        int dex=getdex(s[i]);        if(tree[p].next[dex]==0){            top++;            tree[p].next[dex]=top;        }        p=tree[p].next[dex];    }    tree[p].count++;}void build_ac(){    queue<int> q;    q.push(0);    int i,p,cur,son;    while(!q.empty()){        p=q.front();        q.pop();        for(int i=0;i<4;i++){            if(tree[p].next[i]){                son=tree[p].next[i];                if(p==0)tree[son].fail=p;                else {                    cur=tree[p].fail;                    while(cur&&tree[cur].next[i]==0)cur=tree[cur].fail;                    tree[son].fail=tree[cur].next[i];                }                if(tree[tree[son].fail].count)                    tree[son].count+=tree[tree[son].fail].count;                q.push(son);            }            else   tree[p].next[i]=tree[tree[p].fail].next[i];        }    }}int myhash[41][41][41][41];int ta,tc,tg,tt;int dp[15000][505];void mydp(char *S){    ta=tc=tg=tt=0;    int i,j,k,l,num=0;    for(i=0;S[i]!=0;i++)        if(S[i]=='A')            ta++;        else if(S[i]=='C')            tc++;        else if(S[i]=='G')            tg++;        else            tt++;    for(i=0;i<=ta;i++)        for(j=0;j<=tc;j++)            for(k=0;k<=tg;k++)                for(l=0;l<=tt;l++)                    myhash[i][j][k][l]=num++;}void solve(int ca){   int i,j,k,l,p,q,son,x1,x2,mmax=0;   dp[0][0]=0;   for(i=0;i<=ta;i++)       for(j=0;j<=tc;j++)           for(k=0;k<=tg;k++)               for(l=0;l<=tt;l++)               {                   if(i+j+k+l==0)                       continue;                   x1=myhash[i][j][k][l];                   for(p=0;p<=top;p++)                   {                       for(q=0;q<4;q++)                       {                           if(q==0&&i-1>=0)                               x2=myhash[i-1][j][k][l];                           else if(q==1&&j-1>=0)                               x2=myhash[i][j-1][k][l];                           else if(q==2&&k-1>=0)                               x2=myhash[i][j][k-1][l];                           else if(q==3&&l-1>=0)                               x2=myhash[i][j][k][l-1];                           else                               continue;                           if(dp[x2][p]==-1)                               continue;                           son=tree[p].next[q];                           dp[x1][son]=max(dp[x1][son],dp[x2][p]+tree[son].count);                           if(dp[x1][son]>mmax)                               mmax=dp[x1][son];                       }                   }               }   printf("Case %d: ",ca);   printf("%d\n",mmax);}int main(){    //freopen("cin.txt","r",stdin);    int n,ca=1;    while(cin>>n&&n){        top=0;        memset(dp,-1,sizeof(dp));        while(n--){            scanf("%s",word);            insert(word);        }        build_ac();        scanf("%s",mystring);        mydp(mystring);        solve(ca++);        memset(tree,0,sizeof(tree));    }    return 0;}
build_ac()内和普通的失败指针建立函数相比最重要的两句(//后面的两句):
   if(tree[tree[son].fail].count) ;       //tree[son].count+=tree[tree[son].fail].count;   q.push(son);}else   ;  //tree[p].next[i]=tree[tree[p].fail].next[i];
直接在建立失败指针时就更新各个节点的count。后面的状态转移过程直接使用。
普通的build_ac():
void build_ac(){    queue<int> q;    q.push(0);    int i,p,cur,son;    while(!q.empty()){        p=q.front();        q.pop();        for(int i=0;i<4;i++){            if(tree[p].next[i]){                son=tree[p].next[i];                if(p==0)tree[son].fail=p;                else {                    cur=tree[p].fail;                    while(cur&&tree[cur].next[i]==0)cur=tree[cur].fail;                    tree[son].fail=tree[cur].next[i];                }                if(tree[cur])                    tree[son].fail=0;                q.push(son);            }        }    }}


0 0