【HDU4117】GRE Words-AC自动机+线段树优化DP
测试地址:GRE Words
题目大意:给定一个字符串序列,每个字符串有一个权值。求一个子序列(可以为空),使得序列中前一项总是后一项的子串,并且字符串的权值和最大。
做法:本题需要用到AC自动机+线段树优化DP。
很容易看出一个DP,但我们不能暴力找到可以转移的点,因此我们需要观察能转移到字符串的点的性质。
首先对这些串建出AC自动机,我们发现,可以转移到字符串的点在树上是该字符串的所有前缀点到根的链。这样考虑转移十分麻烦,但是如果我们把转移看成,每处理一个字符串,它就会对它在fail树中的子树上所有的点作出贡献,于是把fail树的DFS序求出来后,这就是一个区间chkmax(即进行这样的操作)的问题。而对于每个字符串转移时,对它的所有前缀节点进行单点询问即可。显然我们可以用线段树维护这样的东西。
于是我们就以的时间复杂度解决了这个问题,其中表示字符串总长。
值得注意的是本题的空间限制很紧,只有32MB,因此trie不能直接开数组存,而是应该用链表一样的形式来用时间换空间。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int T,n,w[20010];
int rt,totp,first[300010],triefirst[300010],tot;
int pt[400010],L[20010],R[20010];
int in[300010],out[300010],tim;
int q[300010],h,t,fail[300010];
int seg[1200010],tag[1200010];
struct edge
{
int v,next;
}e[300010];
struct trieedge
{
int v,next,type;
}ed[300010];
char s[300010];
void trieinsert(int a,int b,int type)
{
ed[++tot].v=b;
ed[tot].next=triefirst[a];
ed[tot].type=type;
triefirst[a]=tot;
}
int ch(int v,int type)
{
for(int i=triefirst[v];i;i=ed[i].next)
if (ed[i].type==type) return ed[i].v;
return -1;
}
void insert(int v,int x,int step,int len)
{
pt[++R[x]]=v;
if (step==len) return;
int nxt=ch(v,s[step]-'a');
if (nxt==-1)
{
nxt=++totp;
trieinsert(v,nxt,s[step]-'a');
}
insert(nxt,x,step+1,len);
}
void insert(int a,int b)
{
e[++tot].v=b;
e[tot].next=first[a];
first[a]=tot;
}
void buildAC()
{
q[1]=rt,h=t=1;
fail[rt]=0;
while(h<=t)
{
int v=q[h++];
for(int x=triefirst[v];x;x=ed[x].next)
{
int i=ed[x].type,p=fail[v];
int y=ed[x].v;
while(p&&ch(p,i)==-1) p=fail[p];
if (p) fail[y]=ch(p,i);
else fail[y]=rt;
insert(fail[y],y);
q[++t]=y;
}
}
}
void dfs(int v)
{
in[v]=++tim;
for(int i=first[v];i;i=e[i].next)
dfs(e[i].v);
out[v]=tim;
}
void pushdown(int no)
{
if (tag[no]>0)
{
seg[no<<1]=max(seg[no<<1],tag[no]);
tag[no<<1]=max(tag[no<<1],tag[no]);
seg[no<<1|1]=max(seg[no<<1|1],tag[no]);
tag[no<<1|1]=max(tag[no<<1|1],tag[no]);
tag[no]=0;
}
}
void pushup(int no)
{
seg[no]=max(seg[no<<1],seg[no<<1|1]);
}
void buildtree(int no,int l,int r)
{
seg[no]=tag[no]=0;
if (l==r) return;
int mid=(l+r)>>1;
buildtree(no<<1,l,mid);
buildtree(no<<1|1,mid+1,r);
}
void modify(int no,int l,int r,int s,int t,int x)
{
if (l>=s&&r<=t)
{
seg[no]=max(seg[no],x);
tag[no]=max(tag[no],x);
return;
}
int mid=(l+r)>>1;
pushdown(no);
if (s<=mid) modify(no<<1,l,mid,s,t,x);
if (t>mid) modify(no<<1|1,mid+1,r,s,t,x);
pushup(no);
}
int query(int no,int l,int r,int x)
{
if (l==r) return seg[no];
int mid=(l+r)>>1;
pushdown(no);
if (x<=mid) return query(no<<1,l,mid,x);
else return query(no<<1|1,mid+1,r,x);
}
int main()
{
scanf("%d",&T);
int Case=0;
while(T--)
{
rt=totp=1;
memset(first,0,sizeof(first));
memset(triefirst,0,sizeof(triefirst));
tot=0;
scanf("%d",&n);
R[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%s%d",s,&w[i]);
L[i]=R[i-1]+1;
R[i]=R[i-1];
insert(rt,i,0,strlen(s));
}
tim=0;
tot=0;
buildAC();
dfs(rt);
buildtree(1,1,totp);
int ans=0;
for(int i=1;i<=n;i++)
{
int mx=0;
for(int j=L[i];j<=R[i];j++)
mx=max(mx,query(1,1,totp,in[pt[j]]));
ans=max(ans,mx+w[i]);
modify(1,1,totp,in[pt[R[i]]],out[pt[R[i]]],mx+w[i]);
}
Case++;
printf("Case #%d: %d\n",Case,ans);
}
return 0;
}