hdu多校第一场 1006 (hdu6583)Typewriter dp/后缀自动机

题意:

有个打字机,在当前字符串后新加一个字花费p,把当前字符串的一个连续子串拷贝到当前字符串的末尾花费q,给定一个字符串,求用打字机打出这个字符串的最小花费。

题解:

容易想到用dp

记dp[i]为打出前i个字符的最小花费,对于每个i,令

A=dp[i-1]+p 

B=dp[j]+q 其中j为最小的,使得s[j+1~i]是s[1~j]的连续子串的值

其实就是,把这个字符串咔嚓切一刀,看后面的部分是不是前面部分的子串,如果不是,就把切的地方向后挪,前面的部分是原串,后面的部分是模式串。

dp[i]=min(A,B)

假如在i=k的时候已经找到了这么一个最优切分点j,那么在讨论i=k+1的时候,这个最优切分点要么就是原来的j,要么就需要把这个j向后挪。

为啥,因为j已经是令原串最短的切分点了,现在你还要往模式串后面加东西,原串不可能更短,只能更长。

注意,在j向后移动的过程中,出现了将模式串的第一位砍掉的操作,那么问题来了,有没有一种数据结构,支持这种将模式串的前面砍掉的操作呢?

后缀自动机。后缀自动机上一个节点代表多个连续子串,其中一个最长,剩下的都是这个最长的连续子串的后缀,父节点又是子节点的后缀,那么,对于某个自动机上的连续子串,把它最前面一位砍掉,要么它还停留在原来的节点上,要么它转移到了父节点上。

后缀自动机增量构造复杂度O(1),状态转移复杂度O(1)

总时间复杂度O(n)

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn=200005;
#define kind=26;
struct state
{
    state *Next[kind],*link;
    int len;
    state()
    {
        link=0;
        len=0;
        memset(Next,0,sizeof(Next));
    }
};
int sz;
state st[maxn*2+5];
inline state* newnode(int len = 0)
{
    memset(st[sz].Next,0,sizeof(st[sz].Next));
    st[sz].link=0;
    st[sz].len=len;
    return &st[sz++];
}
state *root,*last;
void extend(int w)
{
    state* p=last;
    state* cur=newnode(p->len+1);
    while(p&&p->Next[w]==0)
    {
        p->Next[w]=cur;
        p=p->link;
    }
    if(p)
    {
        state* q=p->Next[w];
        if(p->len+1==q->len)
            cur->link=q;
        else
        {
            state* clone=newnode(p->len+1);
            memcpy(clone->Next,q->Next,sizeof(q->Next));
            clone->link=q->link;
            q->link=clone;
            cur->link=clone;
            while(p&&p->Next[w]==q)
            {
                p->Next[w]=clone;
                p=p->link;
            }
        }
    }
    else cur->link=root;
    last=cur;
}

#define ll long long
char s[maxn];
ll dp[maxn];
int main()
{
    while(~scanf("%s",s+1))
    {
        sz=0;
        root=newnode();
        last=root;
        ll p,q;
        scanf("%lld%lld",&p,&q);
        int n=strlen(s+1);
        int j=1;
        dp[1]=p;
        extend(s[1]-'a');
        state* cur=root->Next[s[1]-'a'];
        for(int i=2;i<=n;i++)
        {
            dp[i]=dp[i-1]+p;
            while(1)
            {
                while(cur!=root && cur->link->len>=i-j-1) cur=cur->link;
                if(cur->Next[s[i]-'a']!=NULL)
                {
                    cur=cur->Next[s[i]-'a'];
                    break;
                }
                else
                    extend(s[++j]-'a');
            }
            //cout<<i<<' '<<j<<endl;
            dp[i]=min(dp[i],dp[j]+q);
        }
        printf("%lld\n",dp[n]);
    }
}

PS:本人从来没搞明白subset,substring,section,子串,子序列,区间的区别,因此一般习惯称其为“连续子串/序列”,“非连续子串/子序列”

猜你喜欢

转载自www.cnblogs.com/isakovsky/p/11246450.html