AC自動機+DP小結 (一)

    好久沒有更新博客了,最近真是懶到家了,南京賽前重點複習了下AC自動機+DP方面的題,寫下來總結一下。

HDU 2457  DNA repair

http://acm.hdu.edu.cn/showproblem.php?pid=2457

題目大意:給你一個字符主串和很多病毒串,要求更改最少的字符使得沒有一個病毒串是主串的子串。

 

思路:本人的第一道AC自動機DP,很初等的題目了,首先根據病毒串建立AC自動機,病毒串的尾節點設置一個標記,表示這個節點“危險”不能走,問題轉化到求用主串在建立後的AC自動機上走len步(len爲主串的長度)且不能經過“危險”節點需要更改的最小字符個數,設dp[i][j]表示長度爲i的字符串到達j節點時所需改變的最小字符個數,然後根據AC自動機進行轉移。建立失敗指針的時候要注意的是如果一個節點的失敗指針指向的節點是“危險的”,那麼我們也要把該節點設爲“危險”的,然後就直接狀態轉移即可。

 

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <queue>
#define maxn 1010
using namespace std;
struct node
{
    int next[4];
    int fail;
    int flag;
    void init()
    {
        memset(next,0,sizeof(next));
        fail=0;
        flag=0;
    }
}trie[maxn];
int n,tot,inf;
int dp[maxn][maxn];
void init()
{
    tot=0;
    trie[0].init();
    memset(dp,1,sizeof(dp));
    inf=dp[0][0];
}
int getnum(char x)
{
    switch(x)
    {
        case 'A':return 0;
        case 'C':return 1;
        case 'G':return 2;
        case 'T':return 3;
    }
    return 0;
}
void insert(char *str)
{
    int p=0,index;
    for(;*str!='\0';str++)
    {
        index=getnum(*str);
        if(trie[p].next[index]==0)
        {
            trie[++tot].init();
            trie[p].next[index]=tot;
        }
        p=trie[p].next[index];
    }
    trie[p].flag=1;
}
void build_fail()
{
    queue<int>Q;
    int p,son,cur,i;
    Q.push(0);
    while(!Q.empty())
    {
        p=Q.front();
        Q.pop();
        for(i=0;i<4;i++)
        {
            if(trie[p].next[i]!=0)
            {
                son=trie[p].next[i];
                cur=trie[p].fail;
                if(p==0)
                    trie[son].fail=0;
                else
                {
                    while(cur&&trie[cur].next[i]==0)
                        cur=trie[cur].fail;
                    trie[son].fail=trie[cur].next[i];
                    if(trie[trie[son].fail].flag)
                    trie[son].flag=1;
                }
                Q.push(son);
            }
            else
                {
                  //  if(p==0)
                   // trie[p].next[i]=0;
                  //  else
                    trie[p].next[i]=trie[trie[p].fail].next[i];
                }
        }
    }
}
char str[maxn],tmp[25];
void solve()
{
    dp[0][0]=0;
    int i,j,k,len=strlen(str+1);
    for(i=1;i<=len;i++)
    {
        for(j=0;j<=tot;j++)
        {
            if(dp[i-1][j]!=inf)
            {
                for(k=0;k<4;k++)
                {
                    int son=trie[j].next[k];
                    if(trie[son].flag==0)
                    {
                        dp[i][son]=min(dp[i][son],dp[i-1][j]+(getnum(str[i])!=k));
                    }
                }
            }
        }
    }
    int ans=inf;
    for(i=0;i<=tot;i++)
    {
        if(!trie[i].flag)
        ans=min(ans,dp[len][i]);
    }
    if(ans==inf)
    printf("-1\n");
    else
    printf("%d\n",ans);
}
int main()
{
    // freopen("dd.txt","r",stdin);
     int T=0;
     while(scanf("%d",&n)&&n)
     {
         printf("Case %d: ",++T);
         init();
         for(int i=1;i<=n;i++)
         {
             scanf("%s",tmp);
             insert(tmp);
         }
         build_fail();
         scanf("%s",str+1);
         solve();
     }
    return 0;
}


 

 hdu 2825

http://acm.hdu.edu.cn/showproblem.php?pid=2825

題目大意:給你一些密碼片段字符串,讓你求長度爲n,且至少包含k個不同密碼片段串的字符串的數量。

 

思路:因爲密碼串的數量不多,所以這裏可以用狀壓解決,設dp[i][j][flag]表示長度爲i且在節點j狀態爲flag的字符串有多少個,flag表示了這個狀態包含密碼串的狀態,flag的第x位爲1表示包含了第x個密碼串,否則表示沒有包含,我們構建密碼串的AC自動機,然後在每個節點也維護一個flag表示該節點所表示字符串包含密碼穿的狀態,並且在構建失敗指針的時候,當前節點的flag要與其失敗指針所指結點的flag做一個並集.

即  trie[son].flag=trie[son].flag|trie[trie[son].fail].flag;之後在AC自動機上DP,轉移,最後求至少包含k個1的狀態flag,長度爲len的串有多少個即爲答案。

代碼如下

 

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#include <queue>
#define ll long long
#define maxn 1010
#define knum 26
#define mod 20090717
using namespace std;
struct node
{
    int next[knum];
    int fail;
    int flag;
    void init()
    {
        memset(next,0,sizeof(next));
        fail=0;
        flag=0;
    }
}trie[maxn];
int n,m,k,tot;
ll dp[30][110][1100];
void init()
{
    tot=0;
    trie[0].init();
}
void insert(char *str,int val)
{
    int p=0,index;
    for(;*str!='\0';str++)
    {
        index=*str-'a';
        if(trie[p].next[index]==0)
        {
            trie[++tot].init();
            trie[p].next[index]=tot;
        }
        p=trie[p].next[index];
    }
    trie[p].flag|=(1<<val);
}
void build_fail()
{
    queue<int>Q;
    int p,son,cur,i;
    Q.push(0);
    while(!Q.empty())
    {
        p=Q.front();
        Q.pop();
        for(i=0;i<knum;i++)
        {
            if(trie[p].next[i]!=0)
            {
                son=trie[p].next[i];
                cur=trie[p].fail;
                if(p==0)
                    trie[son].fail=0;
                else
                {
                    while(cur&&trie[cur].next[i]==0)
                    cur=trie[cur].fail;
                    trie[son].fail=trie[cur].next[i];
                }
                trie[son].flag=trie[son].flag|trie[trie[son].fail].flag;
                Q.push(son);
            }
            else
            {
                trie[p].next[i]=trie[trie[p].fail].next[i];
            }
        }
    }
}
int check(int x)
{
    int sum=0;
    while(x)
    {
        sum++;
        x-=x&(-x);
    }
    return sum;
}
void solve()
{

    int i,j,kk,limit=(1<<m);
    for(i=0;i<=n;i++)
    {
        for(j=0;j<=tot;j++)
        {
            for(kk=0;kk<limit;kk++)
            dp[i][j][kk]=0;
        }
    }
    dp[0][0][0]=1;
    for(i=0;i<n;i++)
    {
        for(j=0;j<=tot;j++)
        {
            for(kk=0;kk<limit;kk++)
            {
                if(dp[i][j][kk])
                {
                    //cout<<dp[i][j][k]<<" ";
                    for(int l=0;l<knum;l++)
                    {
                        int son=trie[j].next[l];
                        int tt=kk|trie[son].flag;
                        dp[i+1][son][tt]=(dp[i+1][son][tt]+dp[i][j][kk])%mod;
                    }
                }
            }
        }
    }
    ll ans=0;
    for(i=0;i<=tot;i++)
    {
        for(j=0;j<limit;j++)
        {
            if(check(j)>=k)
            {
                {
                    ans=(ans+dp[n][i][j])%mod;
                }
            }
        }
    }
    printf("%I64d\n",ans);
}
int main()
{
   // freopen("dd.txt","r",stdin);
    int i;
    while(scanf("%d%d%d",&n,&m,&k)&&(n||m||k))
    {
        init();
        char tmp[15];
        for(i=0;i<m;i++)
        {
            scanf("%s",tmp);
            insert(tmp,i);
        }
        build_fail();
        solve();
    }
    return 0;
}


 

hdu 4057 Rescue the Rabbit

 

http://acm.hdu.edu.cn/showproblem.php?pid=4057

題目大意:給你一些基因片段,每個片段有一個權值,現在要你找到一個長度爲l的基因,使得它的權值最大,基因的權值計算方法是,如果有一個基因片段是該基因的子串,則加上該基因片段的權值,但是每種基因片段的權值只計算一次。

 

思路:還是很容易看出是AC自動機加dp的,還是用狀態壓縮來表示字符串包含基因片段的狀態,用dp[i][j][flag]來表示長度爲i,在j節點狀態爲flag是否可以達(因爲每個基因片段只計算一次,所以對於給定的flag其權值一定,所以只要判斷flag是否可達即可)。因爲i*j*flag太大內存不夠,所以這裏要用滾動數組來優化。類似於上一題,在每個節點維護一個狀態flag,然後建立失敗指針的時候與上一題一樣的處理。然後在AC自動機上轉移判斷各個狀態是否可達,最後對於每個可達的狀態計算其相應權值取最大值即可。如果最大權值小於0,不要忘了輸出 No Rabbit after 2012!

 

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#define maxn 1010
#define inf 2100000000
using namespace std;
struct node
{
    int next[4];
    int fail;
    int flag;
    void init()
    {
        memset(next,0,sizeof(next));
        fail=0;
        flag=0;
    }
}a[maxn];
int n,len,tot;
int weight[15];
char keyword[110];
int dp[2][maxn][1<<10];
void init()
{
    tot=0;
    a[0].init();
    memset(dp,0,sizeof(dp));
}
int getnum(char x)
{
    switch(x)
    {
        case 'A':return 0;
        case 'C':return 1;
        case 'G':return 2;
        case 'T':return 3;
    }
    return 0;
}
void insert(char *str,int val)
{
    int p=0,index;
    for(;*str!='\0';str++)
    {
        index=getnum(*str);
        if(a[p].next[index]==0)
        {
            a[++tot].init();
            a[p].next[index]=tot;
        }
        p=a[p].next[index];
    }
    a[p].flag=a[p].flag|(1<<val);
}
void build_fail()
{
    queue<int>Q;
    int p,son,cur,i;
    Q.push(0);
    while(!Q.empty())
    {
        p=Q.front();
        Q.pop();
        for(i=0;i<4;i++)
        {
            if(a[p].next[i]!=0)
            {
                son=a[p].next[i];
                cur=a[p].fail;
                if(p==0)
                    a[son].fail=0;
                else
                {
                    while(cur&&a[cur].next[i]==0)
                        cur=a[cur].fail;
                    a[son].fail=a[cur].next[i];
                }
                a[son].flag=a[son].flag|a[a[son].fail].flag;
                Q.push(son);
            }
            else
                a[p].next[i]=a[a[p].fail].next[i];
        }
    }
}
int getweight(int x)
{
    int i,sum=0;
    for(i=0;i<n;i++)
    {
        if(x&(1<<i))
        sum+=weight[i];
    }
    return sum;
}
void solve()
{
    int i,j,k,l,son,ans,tmp;
    dp[0][0][0]=1;
    for(i=1;i<=len;i++)
    {
        memset(dp[i&1],0,sizeof(dp[i&1]));
        for(j=0;j<=tot;j++)
        {
            for(l=0;l<(1<<n);l++)
            {
                if(dp[(i+1)&1][j][l]!=1)
                continue;
                for(k=0;k<4;k++)
                {
                    son=a[j].next[k];
                    dp[i&1][son][l|a[son].flag]=1;
                }
            }
        }
    }
    ans=-inf;
    for(j=0;j<(1<<n);j++)
    {
        for(i=0;i<=tot;i++)
        {
            if(dp[len&1][i][j]==1)
            {
                tmp=getweight(j);
                ans=max(ans,tmp);
            }
        }
    }
    if(ans<0)
        printf("No Rabbit after 2012!\n");
    else
        printf("%d\n",ans);
}
int main()
{
    //freopen("dd.txt","r",stdin);
    int i;
    while(scanf("%d%d",&n,&len)!=EOF)
    {
        init();
        for(i=0;i<n;i++)
        {
            scanf("%s%d",keyword,&weight[i]);
            insert(keyword,i);
        }
        build_fail();
        solve();
    }
    return 0;
}

 

hdu 4758  Walk Through Squares

 

http://acm.hdu.edu.cn/showproblem.php?pid=4758

 

題目大意:給你兩個串A和B,它們都只由R和L組成,問你含有n個R和m個L且既包含A也包含B的字符串有多少個。

 

思路:很明顯的AC自動機+DP啊。。。爲什麼多校的時候就沒想到呢。。。

狀態很好設計,就是dp[i][j][k][flag]表示長度爲i,在j節點且其中有k個R,狀態爲flag的字符串數量。和上兩道題一樣構造AC自動機後,在AC自動機上走,轉移的時候分別討論當前位是L或R往下走即可,沒啥trick,然後內存開不夠要用滾動數組處理,將長度那一維去掉就行。

代碼僅供參考。。。

#include <iostream>
#include <string.h>
#include <algorithm>
#include <queue>
#include <stdio.h>
#define maxn 2010
#define knum 2
#define inf 2100000000
#define mod 1000000007
#define ll long long
using namespace std;
struct node
{
    int next[knum];
    int fail;
    int flag;
    void init()
    {
        memset(next,0,sizeof(next));
        fail=0;
        flag=0;
    }
}trie[maxn];
int n,tot,m;
int getnum(char x)
{
    if(x=='R')
    return 0;
    return 1;
}
void init()
{
    tot=0;
    trie[0].init();
}
void insert(char *str,int val)
{
    int p=0,index;
    for(;*str!='\0';str++)
    {
        index=getnum(*str);
        if(trie[p].next[index]==0)
        {
            trie[++tot].init();
            trie[p].next[index]=tot;
        }
        p=trie[p].next[index];
    }
    trie[p].flag|=(1<<val);
}
void build_fail()
{
    queue<int>Q;
    int p,son,cur,i;
    Q.push(0);
    while(!Q.empty())
    {
        p=Q.front();
        Q.pop();
        for(i=0;i<knum;i++)
        {
            if(trie[p].next[i]!=0)
            {
                son=trie[p].next[i];
                cur=trie[p].fail;
                if(p==0)
                    trie[son].fail=0;
                else
                {
                    while(cur&&trie[cur].next[i]==0)
                        cur=trie[cur].fail;
                    trie[son].fail=trie[cur].next[i];
                }
                trie[son].flag|=trie[trie[son].fail].flag;
                Q.push(son);
            }
            else
            {
                trie[p].next[i]=trie[trie[p].fail].next[i];
            }
        }
    }
}
ll dp[2][210][110][4];
void solve()
{
    int i,j,k,l;
    int len=n+m;
    for(i=0;i<2;i++)
     for(j=0;j<=tot;j++)
      for(k=0;k<=m;k++)
       for(l=0;l<4;l++)
        dp[i][j][k][l]=0;
        dp[0][0][0][0]=1;
        for(i=0;i<len;i++)
        {
            int t1=i%2,t2=(i+1)%2;
            for(j=0;j<=tot;j++)
             for(k=0;k<=m;k++)
              for(l=0;l<4;l++)
               dp[t2][j][k][l]=0;
            for(j=0;j<=tot;j++)
            {
                int mi=min(m,i);
                for(k=0;k<=mi;k++)
                {
                    for(l=0;l<4;l++)
                    {
                        if(dp[t1][j][k][l])
                        {
                            for(int tt=0;tt<2;tt++)
                            {
                                int son=trie[j].next[tt];
                                int kk=l|trie[son].flag;
                                if(tt)
                                {
                                    dp[t2][son][k][kk]=(dp[t2][son][k][kk]+dp[t1][j][k][l])%mod;
                                }
                                else
                                {
                                    dp[t2][son][k+1][kk]=(dp[t2][son][k+1][kk]+dp[t1][j][k][l])%mod;
                                }
                            }
                        }
                    }
                }
            }
        }
        int tmp=i%2;
        ll ans=0;
        for(i=0;i<=tot;i++)
        {
            ans+=dp[tmp][i][m][3];
        }
        ans%=mod;
        cout<<ans<<endl;
}
int main()
{
  //  freopen("dd.txt","r",stdin);
    int ncase;
    scanf("%d",&ncase);
    while(ncase--)
    {
        init();
        char tmp[110];
        scanf("%d%d",&m,&n);
        scanf("%s",tmp);
        insert(tmp,0);
        scanf("%s",tmp);
        insert(tmp,1);
        build_fail();
        solve();
    }
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章