建立所有单词的AC自动机,对于每个节点的转移,都是从parent[]或者从fail[],fail[fail[]],...得到的。可以看出fail[]的关系形成一棵树,于是问题转化成,不断在节点处插入,询问点到根路径上的最大值,可以利用dfs序列转化用线段树维护。
基本想法是建好AC自动机后,顺便建立fail树,然后用线段树维护操作。
基本原理:从某个节点沿着fail指针走直到根节点过程中遇到的节点都是改串的后缀相同的串,而fail指针构成了一棵fail树,所以一个串可能是哪些串的后缀字串呢?答案是节点对应fail树里的一颗子树。
当我们用AC自动机解决DP 或者 统计问题的时候,如果要支持更新操作,就需要数据结构的帮忙了
比如codeforces 163E,背景是最简单的多串匹配,但是有一个特殊的地方是会删除一些字符串和重新恢复一些字符串,注意到我们在统计的时候其实就是沿着fail指针走,把所有的标记叠加起来,而fail指针构成了一棵fail树,所以我们在求当前节点的fail指针方向有多少个标记的时候不必一层层的fail上去了,对于每个点维护其到根的有效节点的个数即可,
当更新某个点的时候,就相当于这个点的子树到根的有效节点的个数都发生了变化,将树形结构变成线性结构,在线段树中更新即可
1 5 a 1 ab 2 abb 3 baba 5 abbab 8
Case #1: 14
#include<iostream> #include<cstring> #include<cstdio> #include<queue> using namespace std; #define ll long long #define prt(k) ;//cout<<#k"="<<k<<" " inline void Max(int& a,int b) { if(a<b) a=b; } const int N=300012; /************AC Automaton***********/ int ch[N][26],fail[N]; int node[N]; int root,AC_size,L; const int Char=26; int newnode() { memset(ch[L],-1,sizeof ch[L]); L++; return L-1; } void init() { L=0; root=newnode(); } int idx(char a) { return a-'a'; } void insert(char s[],int id) { int n=strlen(s),u=root; for(int i=0;i<n;i++) { int& tmp=ch[u][idx(s[i])]; if(tmp==-1) tmp=newnode(); u=tmp; } node[id]=u; } void BUILD() { queue<int> q; for(int i=0;i<Char;i++) { int &tmp=ch[root][i]; if(tmp==-1) tmp=root; else { fail[tmp]=root; q.push(tmp) ; } } while(!q.empty()) { int u=q.front(); q.pop(); for(int i=0;i<Char;i++) { int& tmp=ch[u][i]; if(tmp==-1) tmp=ch[fail[u]][i]; else { fail[tmp]=ch[fail[u]][i]; q.push(tmp); } } } } /*************End AC Automaton*************/ /*********Segment Tree**************/ int tree[N<<2],lazy[N<<2]; #define lson l,m,rt*2 #define rson m+1,r,rt*2+1 void build(int l,int r,int rt) { tree[rt]=lazy[rt]=0; if(l==r) return; int m=(l+r)/2; build(lson) ; build(rson); } void pushup(int rt) { tree[rt]=max(tree[rt*2],tree[rt*2+1]); } void pushdown(int rt) { if(lazy[rt]) { Max(lazy[rt*2],lazy[rt]); Max(lazy[rt*2+1],lazy[rt]); Max(tree[rt*2],lazy[rt*2]); Max(tree[rt*2+1],lazy[rt*2+1]); lazy[rt]=0; } } void update(int L,int R,int v,int l,int r,int rt) { if(L<=l&&r<=R) { Max(lazy[rt],v); Max(tree[rt],v); return; } pushdown(rt); int m=(l+r)/2; if(L<=m) update(L,R,v,lson); if(R>m) update(L,R,v,rson); pushup(rt); } int query(int L,int l,int r,int rt) { if(l==r) return tree[rt]; pushdown(rt); int m=(l+r)/2; if(L<=m) return query(L,lson); return (query(L,rson)); } /*********End Segment Tree**********/ vector<int> g[N]; int tot,cnt; int Left[N],Right[N]; void dfs(int u) { Left[u]=++tot; for(int i=0;i<g[u].size();i++) { dfs(g[u][i]); } Right[u]=tot; } int dp[N]; char str[N]; int pos[N/10]; int n; void AC(int id,int l,int r) { int u=0,v=0; for(int i=l;i<=r;i++) { u=ch[u][str[i]-'a']; Max(v,query(Left[u],1,tot,1)); } dp[id]+=v; update(Left[node[id]],Right[node[id]],dp[id],1,tot,1); } int main() { int re,ca=1; cin>>re; while(re--) { cin>>n; init(); pos[0]=0; memset(dp,0,sizeof dp); for(int i=1;i<=n;i++) { scanf("%s%d",str+pos[i-1],&dp[i]); insert(str+pos[i-1],i); pos[i]=strlen(str); } BUILD(); for(int i=0;i<L;i++) g[i].clear(); for(int i=1;i<L;i++) { g[fail[i]].push_back(i); } tot=0; dfs(0); build(1,tot,1); int ans=0; for(int i=1;i<=n;i++) { if(dp[i]>0) AC(i,pos[i-1],pos[i]-1); Max(ans,dp[i]); } printf("Case #%d: %d\n",ca++,ans); } }