hdu 5470 Typewriter 后缀自动机+单调队列优化DP

版权声明:本文为博主原创文章,转载请附上原博客链接。 https://blog.csdn.net/Dale_zero/article/details/82950998

题目链接: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;
}

猜你喜欢

转载自blog.csdn.net/Dale_zero/article/details/82950998