洛谷2178 BZOJ4199 NOI2015 品酒大会 SAM 树形dp

题面链接

题意:
给你一个字符串,每个位置有一个权值(可正可负),对于每一个 i ∈ [ 0 , n − 1 ] i\in[0,n-1] i[0,n1],求所有lcp长度为 i i i的后缀的对数,并且求每一对lcp为 i i i的后缀的两个权值相乘的最大值。长度为 i i i的lcp也可以算做长度是 [ 0 − i − 1 ] [0-i-1] [0i1] n &lt; = 1 e 5 n&lt;=1e5 n<=1e5

题解:
怎么说呢,可能我还是后缀数组学的太差了吧。本来是想找后缀数组的题找到的这个题,但是我只会用后缀数组搞给定一个确定的 i i i的答案。

怎么想都是用SAM做,因为建出SAM之后,我们可以用parent树来算答案,我们可以进行树形dp,这样我们在每个点找出它会在哪个深度产生贡献就可以了。具体来说,我们要在树形dp的时候维护最大值、最小值和子树内原串的后缀的个数。SAM上的一个点表示的是 [ l e n [ f a [ i ] ] + 1 , l e n [ i ] ] [len[fa[i]]+1,len[i]] [len[fa[i]]+1,len[i]]的这么多个串的答案,但是由于长度长的答案可以加到长度更短里,所以我们只更新长度是 l e n [ i ] len[i] len[i]的答案,最后再取一个长度的后缀和就好了,维护最大值的就取个后缀长度的最大值。

如果说的不明白,看一眼代码也就知道在干什么了。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,len[600010],ch[600010][26],fa[600010],hed[600010];
int rt=1,lst=1,cnt=1,num;
long long sz[600010],val[600010],mn[600010],mx[600010],ans1[300010],ans2[300010];
char s[300010];
const long long inf=1000000000000000000;
struct node
{
	int to,next;
}a[600010];
inline int read()
{
	int x=0,f=1;
	char s=getchar();
	while(s>'9'||s<'0')
	{
		if(s=='-')
		f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x*f;
}
inline void insert(int x,long long y)
{
	int cur=++cnt,pre=lst;
	lst=cur;
	len[cur]=len[pre]+1;
	sz[cur]=1;
	mn[cur]=mx[cur]=y;
	for(;pre&&!ch[pre][x];pre=fa[pre])
	ch[pre][x]=cur;
	if(!pre)
	fa[cur]=rt;
	else
	{
		int ji=ch[pre][x];
		if(len[ji]==len[pre]+1)
		fa[cur]=ji;
		else
		{
			int gg=++cnt;
			len[gg]=len[pre]+1;
			memcpy(ch[gg],ch[ji],sizeof(ch[ji]));
			fa[gg]=fa[ji];
			fa[ji]=fa[cur]=gg;
			for(;pre&&ch[pre][x]==ji;pre=fa[pre])
			ch[pre][x]=gg;
		}
	}
}
inline void add(int from,int to)
{
	a[++num].to=to;
	a[num].next=hed[from];
	hed[from]=num;
}
inline void dfs(int x)
{
	if(mn[x]==0&&mx[x]==0)
	{
		mn[x]=inf+1;
		mx[x]=-inf-1;
	}
	for(int i=hed[x];i;i=a[i].next)
	{
		int y=a[i].to;
		dfs(y);
		if((mn[x]<=inf&&mx[x]>=-inf)&&(mn[y]<=inf&&mx[y]>=-inf))
		ans2[len[x]]=max(ans2[len[x]],max(mx[x]*mx[y],mn[y]*mn[x]));
		ans1[len[x]]+=sz[x]*sz[y];
		mx[x]=max(mx[x],mx[y]);
		mn[x]=min(mn[x],mn[y]);
		sz[x]+=sz[y];
	}
}
int main()
{
	n=read();
	scanf("%s",s+1);
	for(int i=1;i<=n;++i)
	val[i]=read();
	for(int i=n;i>=1;--i)
	insert(s[i]-'a',val[i]);
	for(int i=2;i<=cnt;++i)
	add(fa[i],i);
	for(int i=n;i>=0;--i)
	ans2[i]=-inf-1;
	dfs(1);
	for(int i=n-1;i>=0;--i)
	{
		ans2[i]=max(ans2[i],ans2[i+1]);
		ans1[i]+=ans1[i+1];
	}
	for(int i=0;i<=n-1;++i)
	{
		if(ans1[i])
		printf("%lld %lld\n",ans1[i],ans2[i]);
		else
		printf("0 0\n");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值