2019 Multi-University Training Contest 1 1006 Typewriter —— 后缀自动机+DP

This way

题意:

给你一串字符串,你现在有一个空串,你可以每次花费q在你的串最后添加一个字符或者在你已经有的字符串中选一个子串放到最后,花费q,问你打印出起始字符串最少需要的花费为多少。

题解:

在这里插入图片描述
就是这么做啊,由于后缀自动机fail指针的特殊性,所以在j++的时候是不能重新算尾指针的位置的,它是跳到fa类中。
一些细节我在程序中解释了

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2e5+1000;
char s[N];
int k,lens;
struct SAM{
	int last,cnt,nxt[N*2][26],fa[N*2],l[N*2],num[N*2];
	int lasrt;//表示当前字符串所匹配的字典树的位置
	void init(){
		last = cnt=1;
		memset(nxt[1],0,sizeof nxt[1]);
		fa[1]=l[1]=num[1]=0;
		lasrt=1;
	}
	int inline newnode(){
		cnt++;
		memset(nxt[cnt],0,sizeof nxt[cnt]);
		fa[cnt]=l[cnt]=num[cnt]=0;
		return cnt;
	}
	void add(int c){
		int p = last;
        int np = newnode();
        last = np;
        l[np] =l[p]+1;
        while (p&&!nxt[p][c]){
            nxt[p][c] = np;
            p = fa[p];
        }
        if (!p){
            fa[np] =1;
        }
        else{
            int q = nxt[p][c];
            if (l[q]==l[p]+1){
                fa[np] =q;
            }
            else{
                int nq = newnode();
                memcpy(nxt[nq],nxt[q],sizeof nxt[q]);
                fa[nq] =fa[q];
                num[nq] = num[q];
                l[nq] = l[p]+1;
                fa[np] =fa[q] =nq;
                while (nxt[p][c]==q){
                    nxt[p][c]=nq;
                    p = fa[p];
                }
            }
        }
	}
	void back_id(int len)//当前面有一个字符被删掉的时候跳回去
	{
	    while(lasrt&&l[fa[lasrt]]>=len)
            lasrt=fa[lasrt];
            /*
                因为l[fa[a]]<l[a],所以可以用这个性质跳到父类.
            */
        lasrt=lasrt?lasrt:1;
        //这里需要判断一下因为会len会<=0
	}
	void add_id(int len,int c)//新进来字符时跳到下一个点
	{
	    lasrt=nxt[lasrt][c];
	    //因为已经必然有这个方向的字典树了,所以不需要判断是否为0
	    back_id(len);
	}
	int is_substr(int nexc)
	{
	    return nxt[lasrt][nexc];
	}
}sam;
char ss[N];
ll dp[N];
int main()
{
    while(~scanf("%s",s))
    {
        ll q,p;
        scanf("%lld%lld",&p,&q);
        lens=strlen(s);
        sam.init();
        int r=0;
        for(int i=0;i<lens;i++)
        {
            dp[i]=(i>=1?dp[i-1]:0)+p;
            while(r<=i/2||(r<=i&&!sam.is_substr(s[i]-'a')))
            {
                sam.add(s[r++]-'a');
                sam.back_id(i-r);
                //由于最前面的字符被删掉了,所以需要跳到父亲类中,为了查看接下来是否可以重新连边
            	//为什么是i-r是因为要跳到父类中呀,i-r+1就可能跳到子类中了
            }
            sam.add_id(i-r+1,s[i]-'a');
            //如果跳到了根或者已经有到i的后缀了,那么就更新自动机中的尾指针
            if(r<=i)
                dp[i]=min(dp[i],dp[r-1]+q);
        }
        printf("%lld\n",dp[lens-1]);
    }
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值