前置技能:AC自动机,线段树,DP
Part 0
这道题很好的体现了Fail数组的作用。
如果我们已经知道了每个字符串包含哪个字符串,那么剩下的就可以用动态规划来解。
状态为:DP[i]表示已经决策了1~i的字符串,并且选i这个字符串时得到的最大重要值。状态转移方程为 D P [ i ] = max ( D P [ j ] ) + v a l [ i ] ( S [ j ] ∈ S [ i ] ) DP[i]=\max(DP[j])+val[i]\ (S[j]∈S[i]) DP[i]=max(DP[j])+val[i] (S[j]∈S[i])。但由于本题是AC自动机,所以这个i一般对应的是i这个字符串在Trie树上的编号。
Part 1
我们如何在有限的时间范围内求出每个字符串包含哪些字符串呢?首先我们先来一种比较暴力的方法。就是把每个DP值都更新在其字符串的最后一个字符在Trie树的位置。(就和模板差不多的)但是这样复杂度就会超。
但是我们观察一下我们进行的跳Fail操作。我们不妨把这看成一颗树,每个节点的Fail连向该节点。那么如果一个字符串X是另一个字符串Y的子串。那么在Fail树中Y的某个前缀的在Trie树上的编号就会是X在Trie上的编号在Fail树上的后代。(这里有两棵树,注意别弄混)注意连边时别忘了和根节点连一条边。
那么对于每一个Y的前缀,他们的子串就是他们在Fail树上的点到根的一段链上的节点对应DP值最大值。
之后维护每个节点在Fail树上的DFS序,利用线段树区间修改单点查询即可。
下面给出暴力与正解这部分代码进行对比。
for(int i=1;i<=n;i++){
int res=0;
for(int j=Ed[i-1]+1;j<=Ed[i];j++){//相当于扫一遍这个字符串
now=son[now][X[j]-'a'];
for(int k=now;k;k=Fail[k])check_max(res,dp[k]);//更新DP值 记为1操作
}
check_max(dp[now],res+val[i]);//记为2操作
check_max(ans,res+val[i]);//更新答案
}//暴力
for(int i=1;i<=n;i++){
int res=0,now=0;
for(int j=Ed[i-1]+1;j<=Ed[i];j++){//记为操作*
now=son[now][X[j]-'a'];
check_max(res,ST.Query(L[now],1));//就是上述1操作的优化
}
int Lx=L[now],Rx=R[now];
ST.Updata(Lx,Rx,res+val[i],1);//就为上述2操作的优化
check_max(ans,res+val[i]);
}
Part 2
由于本题内存限制。。。有点坑。而且HDU的内存计算貌似有点恶心。于是乎就有了许多优化。vector最好不要开(我的代码开了好像过不去)。
memset不可以用,要自己手动清空,上一次用多少这次就清多少。
对于AC自动机第二次遍历字符串时(也就是匹配时),可以把所有字符串存在一个大串里,然后存下每个字符串在大串里的起始位置。(也就是上述代码中的操作*)
本地测内存时过不去的,但是交上去还是能过的。
AC代码:
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstring>
#define N 300005
#define check_max(x,y) x=max(x,y)
using namespace std;
bool cur1;
int tot_edge,head[N];
int n,val[N],Ed[N];
char S[N],X[N];
struct E{
int to,nx;
}edge[N];
void Addedge(int a,int b){
edge[++tot_edge].to=b;
edge[tot_edge].nx=head[a];
head[a]=tot_edge;
}
struct node{
int L,R,mx,Add;
}tree[N<<2];
struct Segment{//线段树
void Build(int L,int R,int p){//当clear用
tree[p].L=L,tree[p].R=R,tree[p].mx=tree[p].Add=0;
if(L==R)return;
int mid=(L+R)>>1;
Build(L,mid,p<<1);
Build(mid+1,R,p<<1|1);
}
void Down(int p){
if(!tree[p].Add)return;
int& res=tree[p].Add;
check_max(tree[p<<1].mx,res);
check_max(tree[p<<1].Add,res);
check_max(tree[p<<1|1].mx,res);
check_max(tree[p<<1|1].Add,res);
res=0;
}
void Updata(int Lx,int Rx,int d,int p){//区间更新
if(Lx<=tree[p].L&&tree[p].R<=Rx){
check_max(tree[p].mx,d);
check_max(tree[p].Add,d);
return;
}
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(Rx<=mid)Updata(Lx,Rx,d,p<<1);
else if(Lx>mid)Updata(Lx,Rx,d,p<<1|1);
else Updata(Lx,mid,d,p<<1),Updata(mid+1,Rx,d,p<<1|1);
tree[p].mx=max(tree[p<<1].mx,tree[p<<1|1].mx);
}
int Query(int x,int p){//单点查询
if(tree[p].L==x&&tree[p].R==x)return tree[p].mx;
Down(p);
int mid=(tree[p].L+tree[p].R)>>1;
if(x<=mid)return Query(x,p<<1);
return Query(x,p<<1|1);
}
}ST;
struct AC_automation{
int son[N][26],Fail[N],tot;
int tot_id,L[N],R[N];//Fail树上DFS序
void clear(){
for(int i=0;i<=tot_id;i++){//用多少清多少
head[i]=Fail[i]=0;
for(int j=0;j<26;j++)son[i][j]=0;
}
tot=tot_id=tot_edge=0;
}
void Insert(int id,int len){
int now=0;
Ed[id]=Ed[id-1];
for(int i=0;i<len;i++){
if(!son[now][S[i]-'a'])son[now][S[i]-'a']=++tot;
now=son[now][S[i]-'a'];
X[++Ed[id]]=S[i];
}
}
void Build(){
queue<int>Q;
for(int i=0;i<26;i++){
if(son[0][i]){
Fail[son[0][i]]=0;
Addedge(0,son[0][i]);//注意一开始还要和0连一条边的
Q.push(son[0][i]);
}
}
while(!Q.empty()){
int now=Q.front();Q.pop();
for(int i=0;i<26;i++){
if(son[now][i]){
Addedge(son[Fail[now]][i],son[now][i]);
Fail[son[now][i]]=son[Fail[now]][i];
Q.push(son[now][i]);
}else son[now][i]=son[Fail[now]][i];
}
}
}
void dfs(int now){//预处理DFS序
L[now]=++tot_id;
for(int i=head[now];i;i=edge[i].nx)dfs(edge[i].to);
R[now]=tot_id;
}
int Solve(){
dfs(0);//根节点为0
ST.Build(1,tot_id,1);
int ans=0;
for(int i=1;i<=n;i++){
int res=0,now=0;
for(int j=Ed[i-1]+1;j<=Ed[i];j++){
now=son[now][X[j]-'a'];
check_max(res,ST.Query(L[now],1));
}
int Lx=L[now],Rx=R[now];
ST.Updata(Lx,Rx,res+val[i],1);
check_max(ans,res+val[i]);
}
return ans;
}
}AC;
bool cur2;
int main(){
int T;
scanf("%d",&T);
for(int Case=1;Case<=T;Case++){
AC.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s%d",S,&val[i]);
AC.Insert(i,strlen(S));
}
AC.Build();
printf("Case #%d: %d\n",Case,AC.Solve());
}
}