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; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步