【后缀数组】[NOI2015]品酒大会

题目

求所有 l e n len len长公共前缀的两子串的方案数和对应权值最大值

题解

容易想到后缀数组 h e i g h t height height l c p lcp lcp
因为如果两子串有 l e n len len长公共前缀,则 1 , 2... , l e n − 1 1,2...,len-1 1,2...,len1都成立
数据结构什么的很烦人,我们就差分吧,查两子串最长的公共前缀,最后倒着累加起来
方案数比较简单,用找不同子串的方式,单调栈,将方案数累加到 a n s 1 [ h e i g h t [ i ] ] ans1[height[i]] ans1[height[i]]
最大值的话,需要同时维护最大和最小(因为还有负数)
在每个找方案数对应区间里找两个相乘最大,也可以在单调栈里多维护几个数组
注意对应过去的权值是 v a l [ s a [ i ] ] val[sa[i]] val[sa[i]]

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e5+10;
const ll INF=1e18+10;
int n,m;
char s[N];
int tp[N],rk[N],sa[N],t[N];
void Qsort()
{
	for(int i=1;i<=m;i++)t[i]=0;
	for(int i=1;i<=n;i++)t[rk[i]]++;
	for(int i=1;i<=m;i++)t[i]+=t[i-1];
	for(int i=n;i>=1;i--)sa[t[rk[tp[i]]]--]=tp[i];
}
void SA()
{
	for(int i=1;i<=n;i++)rk[i]=s[i]-'a'+1,tp[i]=i;m=30;Qsort();
	for(int w=1,p;w<=n;m=p,w<<=1)
	{
		p=0;
		for(int i=n-w+1;i<=n;i++)tp[++p]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)tp[++p]=sa[i]-w;
		Qsort();swap(tp,rk);p=rk[sa[1]]=1;
		for(int i=2;i<=n;i++)rk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])?p:++p;
		if(p==n)return ;
	}
}
int h[N];
void get_h()
{
	int k=0;
	for(int i=1;i<=n;i++)
	{
		if(rk[i]==1)continue;
		int j=sa[rk[i]-1];if(k)k--;
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
		h[rk[i]]=k;
	}
}
ll a[N];
ll ans1[N],ans2[N];
int z[N],p;
ll minn[N],maxx[N];
int L[N],R[N];
void work()
{
	ll ma=-INF,mi=INF;
	for(int i=1;i<=n;i++)ans2[i]=LLONG_MIN;
	z[++p]=1;
	for(int i=2;i<=n;i++)
	{
		ma=mi=a[sa[i-1]];
		while(p&&h[z[p]]>h[i])
		{
			ans2[h[z[p]]]=max(ans2[h[z[p]]],max(1ll*maxx[p]*ma,1ll*minn[p]*mi));
			ma=max(ma,maxx[p]);mi=min(mi,minn[p]);
			R[z[p--]]=i;
		}
		L[i]=z[p];
		z[++p]=i;minn[p]=mi,maxx[p]=ma;
	}
	ma=mi=a[sa[n]];
	while(p)
	{
		ans2[h[z[p]]]=max(ans2[h[z[p]]],max(1ll*maxx[p]*ma,1ll*minn[p]*mi));
		ma=max(ma,maxx[p]);mi=min(mi,minn[p]);
		R[z[p--]]=n+1;
	}
	for(int i=2;i<=n;i++)ans1[h[i]]+=(ll)(R[i]-i)*(i-L[i]);
	for(int i=n-2;i>=0;i--)
	{
		ans1[i]+=ans1[i+1];
		ans2[i]=max(ans2[i],ans2[i+1]);
	}
}
int main()
{
	scanf("%d%s",&n,s+1);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	SA();get_h();
	work();
	for(int i=0;i<n;i++)
	{
		printf("%lld %lld\n",ans1[i],ans1[i]==0?0:ans2[i]);
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值