HDU 6583 Typewriter 后缀自动机优化DP

PS: 奇耻大辱,一年前写过一道名字一模一样的原题,居然没写出来这道题。

Typewriter
Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 108 Accepted Submission(s): 22

Problem Description
One day, Jerry found a strange typewriter. This typewriter has 2 input modes: pay p coins to append an arbitrary single letter to the back, or q coins to copy a substring that has already been outputted and paste it in the back.
Jerry now wants to write a letter to Tom. The letter is a string S which contains only lowercase Latin letters. But as Jerry is not very wealthy, he wants to know the minimum number of coins he needs to write this letter.

Input
This problem contains multiple test cases. Process until the end of file.
For each test case, the first line contains string S (|S|≤2×105,∑|S|≤5×106), consisting of only lowercase Latin letters. And the second line contains 2 integers p and q (1≤p,q<231).

Output
For each test case, output one line containing the minimum number of coins Jerry needs to pay.

Sample Input
abc
1 2
aabaab
2 1

Sample Output
3
6

题意:
给出两种操作,以及两种操作的花费,要求用最小的花费凑出给定的字符串。
解题思路:
比赛的时候想了两个小时没想出来,没get到点子上,主要也是自己很久没刷过后缀自动机相关的题目了。
这种跟子串有关的题目,很容易联想到后缀自动机,要求最小花费,很容易联想到DP。
对于DP[i] 我们需要知道一个最小的j 使得s[j-i]是s[1-j-1]的子串。
那么dp[i]=min(dp[i-1]+q,dp[j-1]+p) ,为什么只要从dp[j-1] 转移呢。 因为dp数组是单调递增的(我就是比赛的时候没想清楚这一点,赛后画个图就出来了)。
至于怎么找这个j 这个就是后缀自动机的经典用法了。
这里官方题解说的就很清楚:
用 SAM 维护 s[1 : j | 1],若 s[1 : j | 1] 中包含 s[j : i + 1],即加入第 i + 1 个字符仍
然能复制,就不需要做任何处理。否则,重复地将第 j 个字符加入后缀自动机并 j = j + 1,
相应维护 s[j : i + 1] 在后缀自动机上新的匹配位置,直到 s[j, i + 1] ∈ s[1, j | 1]。
PS:补题的时候脑抽了,忘记后缀自动机失配该怎么操作了。调了两小时bug。其实代码量很小。

#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int MAX = 2e5+10;
const int N=MAX;
struct Suffix_Automaton {
    int len[N<<1],fa[N<<1],son[N<<1][26];
    int size,last;
    void Init() {
        memset(son[1],0,sizeof son[1]);
        size=last=1;
    }
    void insert(char c) {
        int s=c-'a';
        int p=last,np=++size;
        memset(son[np],0,sizeof son[np]);
        last=np;
        len[np]=len[p]+1;
        for(; p&&!son[p][s]; p=fa[p])son[p][s]=np;
        if(!p)fa[np]=1;
        else {
            int q=son[p][s];
            if(len[p]+1==len[q])fa[np]=q;
            else {
                int nq=++size;
                len[nq]=len[p]+1;
                memcpy(son[nq],son[q],sizeof(son[q]));
                fa[nq]=fa[q];
                fa[q]=fa[np]=nq;
                for(; son[p][s]==q; p=fa[p])son[p][s]=nq;
            }
        }
    }
    int get_next(int s,char ch,int &lens) {
        int id = ch-'a';
        if(son[s][id]){
            lens++;
            return son[s][id];
        }else{
            while(s && !son[s][id]) s= fa[s];
            if(!s) s=1,lens=0;
            else lens=len[s]+1,s=son[s][id];
            return s;
        }
    }
    int getlen(int s,char ch,int &lens) {
        //cout<<len[get_next(s,ch)]<<endl;
        int id = ch-'a';
        if(son[s][id]){
            lens++;
        }else{
            while(s && !son[s][id]) s= fa[s];
            if(!s) s=1,lens=0;
            else lens=len[s]+1,s=son[s][id];
        }
        return lens;
    }
} Sam;
long long dp[MAX];
char str[MAX];
int main() {
   // freopen("02","r",stdin);
    while(~scanf("%s",str+1)) {
        Sam.Init();
        int p,q;
        scanf("%d %d",&p,&q);
        int len = strlen(str+1);
        int now_insert_len=0;
        int now_state=1;
        int maxlen=0;
        int next_state;
        for(int i=1; i<=len; i++) {
            dp[i]=dp[i-1]+p;
            if(Sam.getlen(now_state,str[i],maxlen)+now_insert_len>=i) {
                dp[i]=min(dp[i],dp[now_insert_len]+q);
                now_state=Sam.get_next(now_state,str[i],maxlen);
            } else {
                while(Sam.getlen(now_state,str[i],maxlen)+now_insert_len<i) {
                    Sam.insert(str[now_insert_len+1]);
                    now_insert_len++;
                }
                now_state=Sam.get_next(now_state,str[i],maxlen);
                if(now_insert_len<i)
                    dp[i]=min(dp[i],dp[now_insert_len]+q);
            }
        }
        cout<<dp[len]<<endl;
    }
    return 0;
}
//ababaabbb

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值