【HDU4117】GRE Words-AC自动机+线段树优化DP

测试地址:GRE Words
题目大意:给定一个字符串序列,每个字符串有一个权值。求一个子序列(可以为空),使得序列中前一项总是后一项的子串,并且字符串的权值和最大。
做法:本题需要用到AC自动机+线段树优化DP。
很容易看出一个DP,但我们不能暴力找到可以转移的点,因此我们需要观察能转移到字符串si的点的性质。
首先对这些串建出AC自动机,我们发现,可以转移到字符串si的点在fail树上是该字符串的所有前缀点到根的链。这样考虑转移十分麻烦,但是如果我们把转移看成,每处理一个字符串,它就会对它在fail树中的子树上所有的点作出贡献,于是把fail树的DFS序求出来后,这就是一个区间chkmax(即进行ai=max(ai,b)这样的操作)的问题。而对于每个字符串转移时,对它的所有前缀节点进行单点询问即可。显然我们可以用线段树维护这样的东西。
于是我们就以O(totlogtot)的时间复杂度解决了这个问题,其中tot表示字符串总长。
值得注意的是本题的空间限制很紧,只有32MB,因此trie不能直接开数组存,而是应该用链表一样的形式来用时间换空间。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int T,n,w[20010];
int rt,totp,first[300010],triefirst[300010],tot;
int pt[400010],L[20010],R[20010];
int in[300010],out[300010],tim;
int q[300010],h,t,fail[300010];
int seg[1200010],tag[1200010];
struct edge
{
    int v,next;
}e[300010];
struct trieedge
{
    int v,next,type;
}ed[300010];
char s[300010];

void trieinsert(int a,int b,int type)
{
    ed[++tot].v=b;
    ed[tot].next=triefirst[a];
    ed[tot].type=type;
    triefirst[a]=tot;
}

int ch(int v,int type)
{
    for(int i=triefirst[v];i;i=ed[i].next)
        if (ed[i].type==type) return ed[i].v;
    return -1;
}

void insert(int v,int x,int step,int len)
{
    pt[++R[x]]=v;
    if (step==len) return;
    int nxt=ch(v,s[step]-'a');
    if (nxt==-1)
    {
        nxt=++totp;
        trieinsert(v,nxt,s[step]-'a');
    }
    insert(nxt,x,step+1,len);
}

void insert(int a,int b)
{
    e[++tot].v=b;
    e[tot].next=first[a];
    first[a]=tot;
}

void buildAC()
{
    q[1]=rt,h=t=1;
    fail[rt]=0;
    while(h<=t)
    {
        int v=q[h++];
        for(int x=triefirst[v];x;x=ed[x].next)
        {
            int i=ed[x].type,p=fail[v];
            int y=ed[x].v;
            while(p&&ch(p,i)==-1) p=fail[p];
            if (p) fail[y]=ch(p,i);
            else fail[y]=rt;
            insert(fail[y],y);
            q[++t]=y;
        }
    }
}

void dfs(int v)
{
    in[v]=++tim;
    for(int i=first[v];i;i=e[i].next)
        dfs(e[i].v);
    out[v]=tim;
}

void pushdown(int no)
{
    if (tag[no]>0)
    {
        seg[no<<1]=max(seg[no<<1],tag[no]);
        tag[no<<1]=max(tag[no<<1],tag[no]);
        seg[no<<1|1]=max(seg[no<<1|1],tag[no]);
        tag[no<<1|1]=max(tag[no<<1|1],tag[no]);
        tag[no]=0;
    }
}

void pushup(int no)
{
    seg[no]=max(seg[no<<1],seg[no<<1|1]);
}

void buildtree(int no,int l,int r)
{
    seg[no]=tag[no]=0;
    if (l==r) return;
    int mid=(l+r)>>1;
    buildtree(no<<1,l,mid);
    buildtree(no<<1|1,mid+1,r);
}

void modify(int no,int l,int r,int s,int t,int x)
{
    if (l>=s&&r<=t)
    {
        seg[no]=max(seg[no],x);
        tag[no]=max(tag[no],x);
        return;
    }
    int mid=(l+r)>>1;
    pushdown(no);
    if (s<=mid) modify(no<<1,l,mid,s,t,x);
    if (t>mid) modify(no<<1|1,mid+1,r,s,t,x);
    pushup(no);
}

int query(int no,int l,int r,int x)
{
    if (l==r) return seg[no];
    int mid=(l+r)>>1;
    pushdown(no);
    if (x<=mid) return query(no<<1,l,mid,x);
    else return query(no<<1|1,mid+1,r,x);
}

int main()
{
    scanf("%d",&T);
    int Case=0;
    while(T--)
    {
        rt=totp=1;
        memset(first,0,sizeof(first));
        memset(triefirst,0,sizeof(triefirst));
        tot=0;

        scanf("%d",&n);
        R[0]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%s%d",s,&w[i]);
            L[i]=R[i-1]+1;
            R[i]=R[i-1];
            insert(rt,i,0,strlen(s));
        }

        tim=0;
        tot=0;
        buildAC();
        dfs(rt);
        buildtree(1,1,totp);
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int mx=0;
            for(int j=L[i];j<=R[i];j++)
                mx=max(mx,query(1,1,totp,in[pt[j]]));
            ans=max(ans,mx+w[i]);
            modify(1,1,totp,in[pt[R[i]]],out[pt[R[i]]],mx+w[i]);
        }

        Case++;
        printf("Case #%d: %d\n",Case,ans);
    }

    return 0; 
}
posted @ 2018-09-10 21:46  Maxwei_wzj  阅读(178)  评论(0编辑  收藏  举报