BZOJ2905 背单词 AC自动机 线段树

29 篇文章 0 订阅
19 篇文章 0 订阅

题目链接
由于是权限题,我没有权限号,就挂了一个darkbzoj的链接。

题意:
给你 n n n个字符串,每个串有一个价值,你要从中选出一个价值和最大的子序列,使得前面的串是后面串的子串。 数 据 组 数 &lt; = 10 , 总 串 长 &lt; = 3 e 5 , 单 个 串 长 &lt; = 2 e 4 数据组数&lt;=10,总串长&lt;=3e5,单个串长&lt;=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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值