[Explanations] GREWords (AC automata)
Subject to the effect:
Given a sequence of character strings, strings of different positions of its own weight. Now let you select a sub-sequence so that the sequence in front of the string is a substring of string behind. Please seek the maximum weight of the promoter sequence to meet the conditions. A sequence weight is the weight of all the elements and.
Consider a simple approach DP, provided \ (dp (i) \) represents the selected character string \ (I \) maximum weight value sequence proceeds directly in front of the string enumerated by \ (KMP \) determines whether substring is then transferred complexity \ (O (n ^ 3) \)
Consider optimization at the above practice, well known, ac automata \ (fail \) tree at any node to the root of one strand is some suffix. Then, all the sub-string is a string on which the automatic machines ac all nodes in all of the chain to the root. (= Own certain prefix substring suffix)
To optimize the above \ (dp \) , that requires us to maintain a data structure, making it possible to quickly find the maximum value according to a string of his right to have all of the substring value. Consider such an approach, the \ (fail \) tree out alone, now suppose I have a \ (dp \) value to his update will only affect the sub-tree, taking into account the sub-tree of a continuous period of \ ( DFS \) sequences, we are maintaining a straight segment tree section taken \ (max \) single-point demand \ (max \) can.
Can be directly built out first and then turn fail to update \ (dp \) , so that the shape of the tree to determine fail.
Overall complexity \ (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;
}