HDU 5470 Typewriter(后缀自动机 + 单调队列优化dp)

Typewriter

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)
Total Submission(s): 499    Accepted Submission(s): 161

Problem Description

Nowadays, our senior is typewriting an article for her graduation. But as you know our senior is foolish sometimes, so she needs your help. You can assume senior’s article is a string, and senior’s typewriter provides two operations to typewrite it:
1)  Assume you have typewritten a string s, then you can add a character after s, and the cost of adding each character is given to you.
2)  Assume you have typewritten a string s, you can select a substring of s and copy it, then you can paste it after s once. The cost to select a character is A,and the cost to copy and paste a string are all equal B.
Now, you should help senior to typewrite the article by using the minimum cost, can you help her?

Input

In the first line there is an integer T(1≤T≤100), indicating the number of test cases.
For each test case:
The first line includes a string s (1≤|s|≤105) composed by lowercase letters, indicating the article senior want to typewrite.
The second line includes 26 integers, indicating the cost to add character ‘a’, ‘b’, ‘c’…, and so on.
The third line includes two integers A and B.
All the integers in the input are in the range of [1,109]. It is guaranteed that the total length of s doesn't exceed 1.2×106.

Output

For each test case:
Please output “Case #k: answer”(without quotes) one line, where k means the case number count from 1, and the answer is the minimum cost to typewrite the article.

Sample Input

 

2

abc

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

2 2

aaaa

10 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2

1 1

Sample Output

 

Case #1: 3

Case #2: 17

Source

2015 ACM/ICPC Asia Regional Shanghai Online

大致题意:给你一串字符以及单独打印每一个字母的代价,对于一个字母,你可以选择直接打印,也可以选择利用复制粘贴的方法来完成。对于复制粘贴,每次选择长度为i的字符串的代价是i*A,复制的代价是B,粘贴的代价也是B。即复制并粘贴一个长度为x的字符串的代价是x*A+2*B。现在问把这一整个字符串打印出来的代价是多少。

这道题目在今年三月的时候就开始做了,但是当时一直WrongAnswer没有找到原因,然后最近复习字符串重新做了一下,才终于过了。

首先,我们考虑暴力的做法。我令dp[i]表示打印到第i个字符的最小代价,那么有转移方程dp[i]=min(dp[j]+(i-j)*A+2*B,dp[i-1]+c[s[i]]),其中要满足字符串s[j+1...i]要在字符串s[1...j]中出现过。可以看出,如果我们能够在O(1)的时间复杂度内判断某个串是否在另外一个串中出现过,那么我们完成这个dp的时间复杂度就是O(N^2)的。对于1e5级别的数据范围来说,显然是不行的。

我们定义j为最小的一个位置,可以使得1..j的子串包含j+1..i的子串。经过观察,我们可以发现,这个j一定是单调的。也就是说,随着i的变大,j一定只增不减,这个很好理解。如果对于上一个长度来说最小的位置是j,对于当前决策,增加了一个长度,j肯定是要么增加要么不变。所以我们就可以利用这个单调性,用单调队列来优化这个dp。

那么,现在问题就是,如何保证在复杂度时间内判断某个串中是否包含另外一个串。也即,字符串s中,一个子串是否包含另外一个子串。如此,还是可能需要用到s的所有的子串,所以说还是用后缀自动机。每次,我们维护j,j的移动取决于前面部分的子串是否包含后面的子串。具体来说,对于当前的j,j+1~i-1在自动机上匹配到的位置x,如果对于新的i,自动机上面如果仍然有后继,那么j不动。如果没有,那么我就要考虑移动j了,移动的过程中,把新的s[j+1]添加到自动机中,继续匹配,如果添加到一定位置,发现x有s[i]这个后继了,那么停止;或者说,当j+1~i-1的长度缩短到x的parent指针所指节点的长度的时候,x则发生跳转,直接跳到其最长的后缀那里去。因为在移动的过程中,需要匹配的串也逐渐变短,短到一定程度就会变成原本串的一个最长后缀。就这样已知移动然后匹配,找到j移动到i或者发现点x有s[i]这个后继。

如此,对于当前i确定了j之后,我们就可以修改单调队列中的值,把所有小于j的决策都去掉,然后再选取目前最优的决策更新。最后把当前的状态结果也当作决策加入单调队列中。一边dp一边维护单调队列和j,同时还不断的把字符添加到SAM中。具体操作见代码,说的也不是很详细:

#include<bits/stdc++.h>
#define LL long long
#define mod 1000000007
#define pb push_back
#define lb lower_bound
#define ub upper_bound
#define INF 0x3f3f3f3f
#define sf(x) scanf("%d",&x)
#define sc(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define clr(x,n) memset(x,0,sizeof(x[0])*(n+5))
#define IO ios::sync_with_stdio(0);cin.tie(0); cout.tie(0)
#define file(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
using namespace std;

const int N = 2e5 + 10;

struct node{LL w,id;} q[N];
LL A,B,c[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_T,T;
    cin>>T_T;T=0;
    while(T_T--)
    {
        SAM.init();scanf("%s",s+1);
        for(int i=0;i<26;i++) scanf("%lld",&c[i]);
        scanf("%lld%lld",&A,&B); dp[0]=0; int h=0,t=0,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]+c[ch];
            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];
        }
        printf("Case #%d: %lld\n",++T,dp[strlen(s+1)]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/u013534123/article/details/81778955