题目链接
由于是权限题,我没有权限号,就挂了一个darkbzoj的链接。
题意:
给你
n
n
n个字符串,每个串有一个价值,你要从中选出一个价值和最大的子序列,使得前面的串是后面串的子串。
数
据
组
数
<
=
10
,
总
串
长
<
=
3
e
5
,
单
个
串
长
<
=
2
e
4
数据组数<=10,总串长<=3e5,单个串长<=2e4
数据组数<=10,总串长<=3e5,单个串长<=2e4。
题解:
一看到子串不一定就是SA和SAM的题啊,这个题就是用AC自动机的题。
那么我们就先对于这 n n n个串建出AC自动机。我们考虑如何用AC自动机来判断一个串是否是另一个串的子串。做法是,我们知道,AC自动机上的fail指针的含义是如果当前位置匹配失败,下一次应该在哪个串的基础上继续尝试匹配。那么如果一个串 A A A是另一个串 B B B的子串,那么就意味着 B B B在trie树上的所有节点中,至少有一个在若干次失配后会到达 A A A在trie树上的结束节点。那么如果我们根据fail指针的关系建出fail树, A A A串结束节点在fail树上的子树内一定有一个节点是 B B B串在trie树上的节点。我们可以发现,一个点表示的串一定是fail树中的子节点表示的串的一个子串,同时似乎也是一个后缀。
那么我们考虑针对这个题,我们要如何处理。首先我们还是建出AC自动机,然后建出fail树。我们知道,子树一定是dfs序连续的一段,所以我们先处理出fail树的dfs序。我们从第一个串到最后一个串枚举每一个串,我们首先在从这个串的第一个字符开始在trie树上走,每走到一个点,我们就判断这个点在哪些之前的串的子树里。找到之前所以能成为当前子串后权值和最大的串,用它的权值加上这个串的权值,作为这个串的答案。我们考虑怎么更新,我们有了一个串的答案之后,它所有fail树子树内的点都可以接在它后面,所以应该是子树与当前串的答案取max,把子树变成dfs序连续的一段区间之后可以用线段树来维护。那么在顺着trie树走的过程中进行的查询其实就是在线段树上询问单点的最大值。这样每个串都走一遍,复杂度是 O ( 总 串 长 ∗ l o g ) O(总串长*log) O(总串长∗log)的,可以通过本题。
代码:
#include <bits/stdc++.h>
using namespace std;
int T,n,val[200010],num,fail[300010],hed[300010],cnt,xu[300010],ed[300010];
int f[200010],ans;
vector<char> v[200010];
queue<int> q;
char ss[300010];
struct node
{
int vis[26],fa,c;
}t[300010];
struct edge
{
int to,next;
}a[600010];
struct tree
{
int l,r,mx,tag;
}tr[2000010];
inline void insert(int qwq)
{
int len=v[qwq].size(),cur=1;
for(int i=0;i<len;++i)
{
int x=v[qwq][i]-'a';
if(!t[cur].vis[x])
{
t[cur].vis[x]=++num;
t[t[cur].vis[x]].fa=cur;
t[t[cur].vis[x]].c=x;
}
cur=t[cur].vis[x];
}
}
inline void get_fail()
{
for(int i=0;i<26;++i)
{
if(t[1].vis[i])
{
q.push(t[1].vis[i]);
fail[t[1].vis[i]]=1;
}
}
while(!q.empty())
{
int x=q.front(),cur=x;
q.pop();
if(!fail[x])
{
cur=fail[t[x].fa];
while(1)
{
if(t[cur].vis[t[x].c])
{
cur=t[cur].vis[t[x].c];
break;
}
if(cur==1)
break;
cur=fail[cur];
}
fail[x]=cur;
}
for(int i=0;i<26;++i)
{
if(t[x].vis[i])
q.push(t[x].vis[i]);
}
}
}
inline void add(int from,int to)
{
a[++cnt].to=to;
a[cnt].next=hed[from];
hed[from]=cnt;
}
inline void dfs(int x)
{
xu[x]=++cnt;
for(int i=hed[x];i;i=a[i].next)
{
int y=a[i].to;
dfs(y);
}
ed[x]=cnt;
}
inline void build(int rt,int l,int r)
{
tr[rt].l=l;
tr[rt].r=r;
tr[rt].mx=-2e9;
tr[rt].tag=0;
if(l==r)
return;
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
}
inline void pushdown(int rt)
{
if(tr[rt].tag)
{
tr[rt<<1].tag=max(tr[rt<<1].tag,tr[rt].tag);
tr[rt<<1|1].tag=max(tr[rt<<1|1].tag,tr[rt].tag);
tr[rt<<1].mx=max(tr[rt<<1].mx,tr[rt].tag);
tr[rt<<1|1].mx=max(tr[rt<<1|1].mx,tr[rt].tag);
tr[rt].tag=0;
}
}
inline int query(int rt,int le,int ri)
{
int l=tr[rt].l,r=tr[rt].r;
if(le<=l&&r<=ri)
return tr[rt].mx;
pushdown(rt);
int res=-2e9,mid=(l+r)>>1;
if(le<=mid)
res=max(res,query(rt<<1,le,ri));
if(mid+1<=ri)
res=max(res,query(rt<<1|1,le,ri));
return res;
}
inline void update(int rt,int le,int ri,int y)
{
int l=tr[rt].l,r=tr[rt].r;
if(le<=l&&r<=ri)
{
tr[rt].tag=max(tr[rt].tag,y);
tr[rt].mx=max(tr[rt].mx,y);
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if(le<=mid)
update(rt<<1,le,ri,y);
if(mid+1<=ri)
update(rt<<1|1,le,ri,y);
tr[rt].mx=max(tr[rt<<1].mx,tr[rt<<1|1].mx);
}
int main()
{
scanf("%d",&T);
while(T--)
{
for(int i=1;i<=num;++i)
{
for(int j=0;j<=25;++j)
t[i].vis[j]=0;
t[i].fa=0;
t[i].c=0;
}
num=1;
ans=0;
memset(fail,0,sizeof(fail));
memset(hed,0,sizeof(hed));
memset(xu,0,sizeof(xu));
memset(ed,0,sizeof(ed));
memset(f,0,sizeof(f));
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%s",ss+1);
int x=strlen(ss+1);
for(int j=1;j<=x;++j)
v[i].push_back(ss[j]);
scanf("%d",&val[i]);
for(int j=1;j<=x;++j)
ss[j]=0;
}
for(int i=1;i<=n;++i)
insert(i);
get_fail();
for(int i=1;i<=num;++i)
add(fail[i],i);
cnt=0;
dfs(1);
build(1,1,num);
for(int i=1;i<=n;++i)
{
if(val[i]<=0)
continue;
int ji=v[i].size(),mx=-2e9,cur=1;
for(int j=0;j<ji;++j)
{
int x=v[i][j]-'a';
cur=t[cur].vis[x];
mx=max(mx,query(1,xu[cur],xu[cur]));
}
f[i]=val[i]+max(mx,0);
update(1,xu[cur],ed[cur],f[i]);
}
for(int i=1;i<=n;++i)
ans=max(ans,f[i]);
printf("%d\n",ans);
for(int i=1;i<=n;++i)
v[i].clear();
}
return 0;
}