GRE Words HDU - 4117 Fail树 AC自动机

GRE Words HDU - 4117

前置技能: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]) 。但由于本题是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());
    }
}

猜你喜欢

转载自blog.csdn.net/qq_35320178/article/details/88856560