1. 程式人生 > >hdu 5470 Typewriter 字尾自動機+優先佇列優化DP

hdu 5470 Typewriter 字尾自動機+優先佇列優化DP

這道題是用的是字尾自動機 + 優先佇列優化DP。

正常考慮DP的話,暴力DP需要O(n^2)的複雜度,不可行。這道題用優先佇列優化DP可以到O(n)的複雜度。

用dp【i】表示寫出前i個字元需要的cost。則求dp【i】的時候有兩種轉移情況

①:dp【i-1】+cost【s【i】】。

②:dp【i】=dp【j】+(i-j)*a+2*b (j<i)

我們維護處理前一個字元s【i-1】的相關變數:j,x定義如下:

j:如果dp【i-1】由情況②轉移而來,j代表dp【i-1】=dp【j】+(i-1-j)*a+2*b (j<i-1),若由情況①轉移而來,j=i-1-1=i-2;j即轉移到i-1的dp狀態

x:字尾自動機上包含(或者說代表)子串【j+1。。。i-1】的節點序號。

好了,現在我們開始處理dp【i】:

首先將dp【i】賦值為dp【i-1】+cost【s【i】】,方便之後比較最小值。

然後查詢在後綴自動機上,節點x有沒有值為s【i】的拓展邊,若有,說明前j個字元中包含了子串【j+1,j+2。。。i】,此時對於dp【i】而言,合法的轉移方程如下:

dp【i】=dp【k】+(i-k)*a+2*b  (k∈[j,i-1])

然而節點x並不一定含有值為s【i】的拓展邊,當其沒有的時候,將s【j+1】加入字尾自動機中,同時將x賦值為x的父節點,這樣做的原因有兩點: ①:減小子串長度,因為j自增了1,x若依舊保持原長,當出現s【i】拓展邊的時候會超出i的長度

②:保持瞭如果x有s【i】拓展邊的時候依舊可以轉移到dp【i】;

上述步驟處理完之後,在此判斷x有沒有s【i】的拓展邊,若沒有j再次自增1,知道有了為止。

現在我們處理出了可轉移到dp【i】的狀態集合dp[k,k+1.。。i-1],這時需要選擇使得dp【i】=dp【j】+(i-j)*a+2*b (k<=j<=i-1)最小的j。此時單調佇列就派上了用場。觀察發現每個dp【i】的合法狀態集合的右邊界都是i-1,則在O(n)遍歷的過程中每次向單調佇列中加入s【i】同時對其進行維護。這樣在每一次計算dp值的時候就可以O(1)取出合法區間內的最優解。

維護單調佇列時,即將狀態轉移方程分解一下,dp【i】=dp【j】+(i-j)*a+2*b取出只和j有關的項,即dp【j】-j*a,而其他項只和要更新的i有關或者是常數項,這樣在每一次計算的時候就可以取最小的優先佇列權值用以更新dp值。

#include<bits/stdc++.h>
#define LL long long
#define clr(x,n) memset(x,0,sizeof(x[0])*(n+5))

using namespace std;



const int N = 2e5 + 10;
struct node{LL w,id;} q[N];
LL b,a,num[N],dp[N];
char s[N];



struct Suffix_Automation
{
    int tot,cur;
    struct node{int ch[26],len,fa;} T[N];
    void init(){clr(T,tot);cur=tot=1;}

    void ins(int x,int id)
    {
        int p=cur;cur=++tot;T[cur].len=id;
        for(;p&&!T[p].ch[x];p=T[p].fa) T[p].ch[x]=cur;
        if (!p) {T[cur].fa=1;return;}int q=T[p].ch[x];
        if (T[p].len+1==T[q].len) {T[cur].fa=q;return;}
        int np=++tot; memcpy(T[np].ch,T[q].ch,sizeof(T[q].ch));
        T[np].fa=T[q].fa; T[q].fa=T[cur].fa=np; T[np].len=T[p].len+1;
        for(;p&&T[p].ch[x]==q;p=T[p].fa) T[p].ch[x]=np;
    }

} SAM;





int main()
{
    int T;
    scanf("%d",&T);
    for(int cas=1;cas<=T;cas++)
    {

        SAM.init();scanf("%s",s+1);
        for(int i=0;i<26;i++)scanf("%lld",&num[i]);
        scanf("%lld%lld",&a,&b);dp[0]=0;
        int h=0,t=0;
        int len=0;
        for(LL i=1,j=0,x=1;s[i];i++)
        {
            int ch=s[i]-'a';
            int fa=SAM.T[x].fa;
            int nxt=SAM.T[x].ch[ch];

            dp[i]=dp[i-1]+num[ch];
//            printf("%lld\n",dp[i]);
            while(!nxt && j+1<i)
            {
                if(x!=1 && --len==SAM.T[fa].len)
                {
                    x=fa;
                    fa=SAM.T[x].fa;
                }
                j++;
                SAM.ins(s[j]-'a',j);
                nxt=SAM.T[x].ch[ch];
            }

            if(!nxt)
            {
                x=1,h=t=0,j++;
                SAM.ins(s[j]-'a',j);
            }
            else x=nxt,len++;
                while(h!=t&&q[h].id<j) h++;
                if (h!=t) dp[i]=min(dp[i],q[h].w+a*i+b+b);
                q[t++]=node{dp[i]-a*i,i};
                while(h+1!=t&&q[t-1].w<=q[t-2].w) t--,q[t-1]=q[t];


//            while(h1!=t1 && j>q[h1].id) ++h1;
//            if(h1!=t1)dp[i]=min(dp[i],q[h1].w+1LL*i*a+b+b);
////printf("dp[%d]=%lld x=%d\n",i,dp[i],c[h1]);
//            q[t1++]={dp[i]-1LL*a*i,i};
//
//            while(h1+1!=t1 && q[t1-1].w<=q[t1-2].w)
//            {
//                --t1;
//                q[t1-1]=q[t1];
//            }
        }
        printf("Case #%d: %lld\n",cas,dp[strlen(s+1)]);
    }
    return 0;
}