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;
}