——本题来自杭电多校第一场
题意:给定一个字符串,主角需要用打字机将字符串打出来,每次可以:
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掉