【题解】GREWords(AC自动机)
题目大意:
给定一个由字符串构成的序列,不同位置的字符串有自己权值。现在让你选出一个子序列,使得在这个子序列中,前面的串是后面的串的子串。请你求满足条件的子序列的权值的最大值。一个子序列权值是所有元素权值的和。
考虑一个朴素的DP做法,设\(dp(i)\)表示选择了字符串\(i\)的最大权值子序列,转移直接枚举前面的字符串,通过\(kmp\)判断是否为子串,然后转移,复杂度\(O(n^3)\)
考虑优化一下上面的做法,众所周知,ac自动机\(fail\)树上任意节点到根一条链是一段后缀。那么,一个字符串的所有子串就是它在ac自动机上的所有节点的所有到根链。(子串=自己某段前缀的后缀)
要优化上面那个\(dp\),就是要求我们维护一个数据结构,使得可以根据一个字符串快速查询已有的他所有的子串的权值的最大值。考虑这样一种做法,把\(fail\)树单独拿出来,现在假设我有个\(dp\)值更新就只会对他的子树有影响,考虑到子树的一段连续的\(dfs\)序列,所以直接线段树维护一个区间取\(max\)单点求\(max\)即可。
可以直接先把fail建出来再依次更新\(dp\),使得fail树的形态确定。
总复杂度\(O(n\log n)\)
//@winlere #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<vector> #include<queue> using namespace std; typedef long long ll; inline int qr(){ register int ret=0,f=0; register char c=getchar(); while(c<48||c>57)f|=c==45,c=getchar(); while(c>=48&&c<=57) ret=ret*10+c-48,c=getchar(); return f?-ret:ret; } const int maxn=3e5+5; const int inf=1e9+5; int to[maxn],w[maxn]; char c[maxn]; string sav[maxn]; namespace Graph{} namespace ACautomaton{} namespace SegmentTree{int n,que(const int&,const int&,const int&,const int&);} namespace Graph{ vector<int> e[maxn]; int dfn[maxn],End[maxn],timer; inline void add(const int&fr,const int&to){ e[fr].push_back(to); e[to].push_back(fr); } void dfs(const int&now,const int&last){ dfn[now]=++timer; for(auto t:e[now]) if(t!=last) dfs(t,now); End[now]=timer; } inline void init(){for(int t=0;t<maxn;++t) e[t].clear(); timer=0;} } namespace ACautomaton{ using Graph::add; struct E{ int son[26],fail; E(){memset(son,0,sizeof son); fail=0;} inline int& operator [](int x){return son[x];} inline int& operator [](char x){return son[x-'a'];} }ac[maxn]; int cnt; inline void add(const char*data,const int&n,const int&id){ int now=0; for(int t=1;t<=n;++t){ if(!ac[now][data[t]]) ac[now][data[t]]=++cnt; now=ac[now][data[t]]; } to[id]=now; } queue<int> q; inline void gen(){ queue<int>().swap(q); for(char c='a';c<='z';++c) if(ac[0][c]) q.push(ac[0][c]),ac[ac[0][c]].fail=0; while(q.size()){ int now=q.front(); q.pop(); for(char c='a';c<='z';++c){ if(ac[now][c]) ac[ac[now][c]].fail=ac[ac[now].fail][c],q.push(ac[now][c]); else ac[now][c]=ac[ac[now].fail][c]; } } for(int t=1;t<=cnt;++t) add(t,ac[t].fail); } inline void init(){memset(ac,0,sizeof ac); memset(to,0,sizeof to); memset(w,0,sizeof w); cnt=0;} inline int getans(const string&f){ int now=0,ret=0; for(const auto&t:f) ret=max(ret,SegmentTree::que(Graph::dfn[now=ac[now][t]],1,SegmentTree::n,1)); return ret; } } namespace SegmentTree{ #define pp(pos) seg[pos].val=max(seg[pos<<1].val,seg[pos<<1|1].val) #define mid ((l+r)>>1) #define lef l,mid,pos<<1 #define rgt mid+1,r,pos<<1|1 struct E{ int val,tag; E(){val=tag=0;} E(const int&a,const int&b){val=a; tag=b;} }seg[maxn<<2]; inline void pd(const int&pos){ if(seg[pos].tag==0) return; seg[pos<<1].val=max(seg[pos<<1].val,seg[pos].tag); seg[pos<<1|1].val=max(seg[pos<<1|1].val,seg[pos].tag); seg[pos<<1].tag=max(seg[pos<<1].tag,seg[pos].tag); seg[pos<<1|1].tag=max(seg[pos<<1|1].tag,seg[pos].tag); seg[pos].tag=0; } void build(const int&l,const int&r,const int&pos){ seg[pos].val=seg[pos].tag=0; if(l==r) return; build(lef); build(rgt); } void upd(const int&L,const int&R,const int&k,const int&l,const int&r,const int&pos){ if(L>r||R<l) return; if(L<=l&&r<=R) {seg[pos].val=max(seg[pos].val,k); seg[pos].tag=max(seg[pos].tag,k); return;} pd(pos); upd(L,R,k,lef); upd(L,R,k,rgt); pp(pos); } int que(const int&k,const int&l,const int&r,const int&pos){ if(k<l||k>r) return 0; if(l==r) return seg[pos].val; pd(pos); int ret=max(que(k,lef),que(k,rgt)); pp(pos); return ret; } inline void init(const int&a){n=a;build(1,n,1);} #undef lef #undef rgt #undef mid } int main(){ int T=qr(),F=0; while(T--){ ACautomaton::init(); Graph::init(); int n=qr(); for(int t=1;t<=n;++t){ scanf("%s",c+1); sav[t]=c+1; ACautomaton::add(c,strlen(c+1),t); w[t]=qr(); } ACautomaton::gen(); Graph::dfs(0,0); SegmentTree::init(Graph::timer); int ans=0; for(int t=1;t<=n;++t){ int k=w[t]+ACautomaton::getans(sav[t]); SegmentTree::upd(Graph::dfn[to[t]],Graph::End[to[t]],k,1,SegmentTree::n,1); ans=max(k,ans); } printf("Case #%d: %d\n",++F,ans); } return 0; }