BZOJ 2905: 背单词 AC自动机+fail树+dfs序+线段树

Description

给定一张包含N个单词的表,每个单词有个价值W。要求从中选出一个子序列使得其
中的每个单词是后一个单词的子串,最大化子序列中W的和。 
 

Input

第一行一个整数TEST,表示数据组数。 
接下来TEST组数据,每组数据第一行为一个整数N。 
接下来N行,每行为一个字符串和一个整数W。 

Output

TEST行,每行一个整数,表示W的和的最大值。 
 
 
数据规模 
设字符串的总长度为Len 
30.的数据满足,TEST≤5,N≤500,Len≤10^4 
100.的数据满足,TEST≤10,N≤20000,Len≤3*10^5

题解: 

感觉很多 AC 自动机的套路都是将 $trie$ 和 $fail$ 树结合,然后在 $fail$ 树上维护一些东西.
对于本题,首先可以排除掉那些权值小于等于 $0$ 的字符串(出题人是认真的吗?)
构建出来所有单词的 $fail$ 树后,依次枚举每一个字符串,记该字符串在 $trie$ 树中终止节点为 $end(i)$.
那么,如果该单词包含了之前的一个单词,那么后缀就可能来自 $trie$ 树中根节点到该点.
依次枚举这条路径上的点,查询这条路径上在节点在 $fail$ 树对应的 $dfs$ 序上查询一下最大值.
而所有这些值的极大值就是 $i$ 为最后一个串的答案.
考虑 $i$ 可以对后面哪些串有贡献:就是 $fail$ 树中 $i$ 子树内的所有点,这个用线段树维护 $dfs$ 序即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <cstdio>
#include <queue> 
#include <map>  
#include <algorithm> 
#include <cstring>
#define N 300002
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
int T,cas; 
struct Seg {
    #define lson (now<<1|1)
    #define rson (now<<1)  
    struct Node {
        int tag;
    }t[N<<2]; 
    void update(int l,int r,int now,int L,int R,int v) {
        if(l>=L&&r<=R) {
            t[now].tag=max(t[now].tag, v);   
            return;
        
        int mid=(l+r)>>1;
        if(L<=mid) update(l,mid,lson,L,R,v);
        if(R>mid) update(mid+1,r,rson,L,R,v);    
    }
    int query(int l,int r,int now,int p,int pre) {
        pre=max(pre, t[now].tag);    
        if(l==r) return pre;       
        int mid=(l+r)>>1;   
        if(p<=mid) return query(l,mid,lson,p,pre);
        else return query(mid+1,r,rson,p,pre);    
    }  
    #undef lson
    #undef rson
}seg;
struct Node {
    int f, ch[27];    
}t[N];
queue<int>q;
char str[N];
int n,tot,tim,edges,w[N],endpos[N],hd[N],nex[N],to[N],dfn[N],size[N];
void addedge(int u,int v) {
    nex[++edges]=hd[u],hd[u]=edges,to[edges]=v;
}
void dfs(int u) {
    dfn[u]=++tim,size[u]=1;
    for(int i=hd[u];i;i=nex[i]) dfs(to[i]), size[u]+=size[to[i]];     
}
int insert() {
    int len=strlen(str+1),i,rt=0;
    for(i=1;i<=len;++i) {
        if(!t[rt].ch[str[i]-'a']) t[rt].ch[str[i]-'a']=++tot; 
        rt=t[rt].ch[str[i]-'a'];     
    
    return rt;
}
void build() {
    int i,j;
    for(i=0;i<27;++i) if(t[0].ch[i]) q.push(t[0].ch[i]);
    while(!q.empty()) {
        int u=q.front();q.pop();
        for(i=0;i<27;++i) {
            int p=t[u].ch[i];
            if(!p) {
                t[u].ch[i]=t[t[u].f].ch[i]; 
                continue;
            }
            t[p].f=t[t[u].f].ch[i];  
            q.push(p);  
        }
    }     
}
void solve() {
    int i,j; 
    scanf("%d",&n);    
    for(i=1;i<=n;++i) {
        scanf("%s%d",str+1,&w[i]);
        if(w[i]>0) endpos[i]=insert();
    }    
    build();
    for(i=1;i<=tot;++i)
        addedge(t[i].f,i);
    dfs(0);        
    int answer=0;
    for(i=1;i<=n;++i) {
        if(w[i]<=0) continue;      
        int p=endpos[i],re=0;
        while(p) re=max(re, seg.query(1,tim,1,dfn[p],0)), p=t[p].f; 
        re+=w[i]; 
        answer=max(answer, re);    
        seg.update(1,tim,1,dfn[endpos[i]],dfn[endpos[i]]+size[endpos[i]]-1,re);       
    }  
    printf("%d\n",answer);       
    memset(hd,0,sizeof(hd)), memset(endpos,0,sizeof(endpos)), memset(t,0,sizeof(t));
    tot=tim=edges=0; 
    memset(seg.t,0,sizeof(seg.t));     
}
int main() {
    // setIO("input"); 
    scanf("%d",&T);
    for(cas=1;cas<=T;++cas) solve();
    return 0;   
}

  

posted @   EM-LGH  阅读(282)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示