——本題來自杭電多校第一場
題意:給定一個字符串,主角需要用打字機將字符串打出來,每次可以:
1.花費p來打出任意一個字符
2.花費q來將已經打出的某一段(子串)復制到后面去
對於這種最優化的問題,我們可以考慮dp
設置dp[i]表示已經打出前i個字符的最小花費,這樣設狀態是沒有后效性的。
那么顯然有:
dp[i] = dp[i-1] + p
這樣就可以將第一種方案的轉移算出來了
對於第二種方案,我們可以考慮維護一個j,使得 s[j+1……i] 是 s[1……j] 的子集(1),也就是說 s[j+1……i] 可以由 s[1……j] 中的一部分復制而來
具體實現利用后綴自動機來維護 s[1……j] 這個字符串,當不滿足上述條件(1)時,就不斷往后添加字符,並讓 j = j + 1
當滿足上述條件(1)時,就可以有:
dp[i] = min(dp[i], dp[j] + q)
具體細節:當找到滿足條件的j時,記錄在后綴自動機上最后的匹配位置,每次i或者j變化的時候,檢查cur的link指針指向位置的終點集合長度是否大於等於已匹配長度,如果是,就可以往link指針方向跳(對於這一部分不理解的話建議復習SAM的終點集合的性質),之后繼續匹配。
代碼:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 using namespace std; 5 const int maxn=200005; 6 const int kind=26; 7 struct state 8 { 9 state *Next[kind],*link; 10 int len; 11 state() 12 { 13 link=0; 14 len=0; 15 memset(Next,0,sizeof(Next)); 16 } 17 }; 18 int sz; 19 state st[maxn*2+5]; 20 inline state* newnode(int len = 0) 21 { 22 memset(st[sz].Next,0,sizeof(st[sz].Next)); 23 st[sz].link=0; 24 st[sz].len=len; 25 return &st[sz++]; 26 } 27 state *root,*last; 28 void extend(int w) 29 { 30 state* p=last; 31 state* cur=newnode(p->len+1); 32 while(p&&p->Next[w]==0) 33 { 34 p->Next[w]=cur; 35 p=p->link; 36 } 37 if(p) 38 { 39 state* q=p->Next[w]; 40 if(p->len+1==q->len) 41 cur->link=q; 42 else 43 { 44 state* clone=newnode(p->len+1); 45 memcpy(clone->Next,q->Next,sizeof(q->Next)); 46 clone->link=q->link; 47 q->link=clone; 48 cur->link=clone; 49 while(p&&p->Next[w]==q) 50 { 51 p->Next[w]=clone; 52 p=p->link; 53 } 54 } 55 } 56 else cur->link=root; 57 last=cur; 58 } 59 60 #define ll long long 61 char s[maxn]; 62 ll dp[maxn]; 63 int main() 64 { 65 while(~scanf("%s",s+1)) 66 { 67 sz=0; 68 root=newnode(); 69 last=root; 70 ll p,q; 71 scanf("%lld%lld",&p,&q); 72 int n=strlen(s+1); 73 int j=1; 74 dp[1]=p; 75 extend(s[1]-'a'); 76 state* cur=root->Next[s[1]-'a']; 77 for(int i=2;i<=n;i++) 78 { 79 dp[i]=dp[i-1]+p; 80 while(1) 81 { 82 while(cur!=root && cur->link->len>=i-j-1) cur=cur->link; 83 if(cur->Next[s[i]-'a']!=NULL) 84 { 85 cur=cur->Next[s[i]-'a']; 86 break; 87 } 88 else 89 extend(s[++j]-'a'); 90 } 91 //cout<<i<<' '<<j<<endl; 92 dp[i]=min(dp[i],dp[j]+q); 93 } 94 printf("%lld\n",dp[n]); 95 } 96 }
注:由於本代碼的sam用的是指針,HDU中G++編譯器的指針為8字節,所以交G++的時候可能會因為常數太大而T,但是交C++可以600ms左右A掉
