6583 Typewriter && 5470 Typewriter

后缀自动机博客推荐:

https://blog.csdn.net/qq_35649707/article/details/66473069

https://blog.csdn.net/liyuanshuo_nuc/article/details/53561527

T6583

关键:

        如何沿着后缀链跑很重要,直接每次暴力的从后缀链往前跑的话,会t(原因是:aaaaaaaaaaaaa)全是一种字符的数据。复杂度高达O(n^2)。所以定义一个now,先从当前值往后匹配看是否有相同的子串,没有则往后缀链跑,有则直接往前匹配。最后就是要记录每个状态终结点不止一个,我们取最靠前的那个(记录在tag[ ] 数组),以免复制的子串出现重复。

#include"bits/stdc++.h"
#define debug(x) cout<<"["<<#x<<" = "<<x<<"]"<<endl;
using namespace std;
typedef long long LL;
const int MAX = 4e5+7;
const int maxn = 4e5+7;
const int N=MAX;
int q[MAX],head,tail;
LL c[MAX],dp[MAX];
char str[MAX];
LL A,B,P,Q;

struct SAM
{
    // ch 转移函数, pre父亲, len最长长度, tag前缀标记, in入度, endnum endpos的个数 o 顺序
    int ch[maxn][26],pre[maxn],len[maxn],tag[maxn];
    int last, tot, now;
    void init(){
        now = last = tot = 0;
        memset(ch[0], -1, sizeof ch[0]);
        pre[0] = -1; len[0] = 0; tag[0] = 0;
    }
    // 加多个串时为了保证不跨串算
    void reset() {last = 0;}
    int extend(int c, int ind){
        int p = last, np = ++tot;
        len[np] = len[p] + 1; tag[np] = ind; //添加
        memset(ch[np], -1, sizeof ch[np]);
        while(~p && ch[p][c] == -1) ch[p][c] = np, p = pre[p];
        if(p == -1) {
            pre[np] = 0;
        }
        else{
            int q = ch[p][c];
            if(len[q] != len[p] + 1){
                int nq = ++tot;
                memcpy(ch[nq], ch[q], sizeof ch[q]);

                len[nq] = len[p] + 1;
                pre[nq] = pre[q];
                pre[q] = pre[np] = nq;
                tag[nq] = min(tag[np],tag[q]);  //添加
                tag[pre[q]] = min(tag[pre[q]],tag[nq]);   //添加
                while(~p && ch[p][c] == q) ch[p][c] = nq, p = pre[p];
            }
            else {
                pre[np] = q;
            }
        }
        last = np;
        return len[np] - len[pre[np]];
    }

    void get_next(int c, int k) {
        while((~pre[now]) && !ch[now][c]) now = pre[now];
        now = ch[now][c];
        while((~pre[now]) && tag[now] + len[pre[now]]+1 > k ) now = pre[now];
        int j = tag[now] + len[now] > k? tag[now]: k-len[now];
        dp[k] = min(dp[k],dp[j]+Q);
    }
}sam;

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
    while(~scanf("%s",str+1)) {
        scanf("%lld%lld",&P,&Q);

        sam.init(); head = 1, tail = 0;
        int len = strlen(str+1);
        for(int i=1; i<=len; i++) {
            int cv = str[i] - 'a';
            dp[i]=dp[i-1]+P;
            sam.extend(cv,i);
            sam.get_next(cv,i);
        }
        printf("%lld\n",dp[len]);
    }
    return 0;
}

T5470 

关键:

        因为A和B的关系导致取最靠前的一段复制并不能得到最优解,最靠前往后的值也有可能树答案,但是由于下标 i 的最靠前转移位置一定 <=  下标 i+1 最靠前的转移位置。所以用单调队列优化dp。

#include"bits/stdc++.h"
#define debug(x) cout<<"["<<#x<<" = "<<x<<"]"<<endl;
using namespace std;
typedef long long LL;
const int MAX = 4e5+7;
const int maxn = 4e5+7;
const int N=MAX;
int q[MAX],head,tail;
LL c[MAX],dp[MAX];
char str[MAX];
LL A,B;

struct SAM
{
    // ch 转移函数, pre父亲, len最长长度, tag前缀标记, in入度, endnum endpos的个数 o 顺序
    int ch[maxn][26],pre[maxn],len[maxn],tag[maxn];
    int last, tot, now;
    void init(){
        now = last = tot = 0;
        memset(ch[0], -1, sizeof ch[0]);
        pre[0] = -1; len[0] = 0; tag[0] = 0;
    }
    // 加多个串时为了保证不跨串算
    void reset() {last = 0;}
    int extend(int c, int ind){
        int p = last, np = ++tot;
        len[np] = len[p] + 1; tag[np] = ind; //添加
        memset(ch[np], -1, sizeof ch[np]);
        while(~p && ch[p][c] == -1) ch[p][c] = np, p = pre[p];
        if(p == -1) {
            pre[np] = 0;
        }
        else{
            int q = ch[p][c];
            if(len[q] != len[p] + 1){
                int nq = ++tot;
                memcpy(ch[nq], ch[q], sizeof ch[q]);

                len[nq] = len[p] + 1;
                pre[nq] = pre[q];
                pre[q] = pre[np] = nq;
                tag[nq] = min(tag[np],tag[q]);  //添加
                tag[pre[q]] = min(tag[pre[q]],tag[nq]);   //添加
                while(~p && ch[p][c] == q) ch[p][c] = nq, p = pre[p];
            }
            else {
                pre[np] = q;
            }
        }
        last = np;
        return len[np] - len[pre[np]];
    }

    void get_next(int c, int k) {
        while((~pre[now]) && !ch[now][c]) now = pre[now];
        now = ch[now][c];
        while((~pre[now]) && tag[now] + len[pre[now]]+1 > k ) now = pre[now];

        int j = tag[now] + len[now] > k? tag[now]: k-len[now];
        while(head <= tail && q[head] < j) ++head;
        if(head <= tail) dp[k] = min(dp[k],dp[q[head]]+A*(k-q[head])+B+B);
        while(head <= tail && dp[k]-A*k <= dp[q[tail]]-A*q[tail]) --tail;
        q[++tail]=k;
    }
}sam;

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
#endif
    int T,cas = 0;
    scanf("%d",&T);
    while(T--) {
        scanf("%s",str+1);
        for(int i = 0; i < 26; i++) {
            scanf("%d",&c[i]);
        }
        scanf("%lld%lld",&A,&B);

        sam.init(); head = 1, tail = 0;
        int len = strlen(str+1);
        for(int i=1; i<=len; i++) {
            int cv = str[i] - 'a';
            dp[i]=dp[i-1]+c[cv];
            sam.extend(cv,i);
            sam.get_next(cv,i);
        }
        printf("Case #%d: %lld\n",++cas,dp[len]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值