题意:
给你一串字符串,你现在有一个空串,你可以每次花费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;
}