题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5470
这道题是用的是后缀自动机 + 单调队列优化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;
}