【NOI2015】品酒大会【后缀数组】【并查集】

本文介绍了一种利用后缀数组和并查集解决字符串匹配问题的算法。通过预处理得到高度数组,对数组进行倒序排序,并使用并查集合并区间,同时维护最大值、次大值、最小值和次小值来处理包含负数的情况,实现了对方案数和最大乘积的高效计算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门

传送门

题意:给一个长度为 N N N的字符串和一个长度为 N N N的序列 A A A,对于所有的 k ∈ [ 0 , N − 1 ] k \in [0,N-1] k[0,N1],求选出两个数 i , j i,j i,j满足 l c p ( s u f f i x ( i ) , s u f f i x ( j ) ) ≥ k lcp(suffix(i),suffix(j))\geq k lcp(suffix(i),suffix(j))k的方案数和 A i × A j A_i \times A_j Ai×Aj的最大值

数据范围:暴力跑不过

终于有个后缀自动机做不了的了

看到后缀的前缀,显然是后缀数组

显然求出 h e i g h t height height数组,倒着排序

显然这是单调的

读到一个长度时,就把 i i i i − 1 i-1 i1合并并统计答案

显然可以用并查集

对于方案数,维护一个 s i z e size size ,合并的时候相乘再计入答案

对于 A i × A j A_i \times A_j Ai×Aj的最大值,维护当前 A A A的最大值和次大值

然而有负数。根据意识流,再维护个最小值和次小值

瞎维护一通,信仰 A C AC AC

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 300005
using namespace std;
typedef long long ll;
const ll INF=1e18;
int sa[MAXN],rk[MAXN],tp[MAXN];
int c[MAXN],n,m='z';
int ht[MAXN];
char s[MAXN];
inline void Rsort()
{
	for (int i=1;i<=m;i++) c[i]=0;
	for (int i=1;i<=n;i++) ++c[rk[i]];
	for (int i=1;i<=m;i++) c[i]+=c[i-1];
	for (int i=n;i>=1;i--) sa[c[rk[tp[i]]]--]=tp[i];
}
void SA()
{
	for (int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
	Rsort();
	for (int w=1,p=0;p<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;
		Rsort();
		swap(rk,tp),rk[sa[1]]=p=1;
		for (int i=2;i<=n;i++)
			if (tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w]) rk[sa[i]]=p;
			else rk[sa[i]]=++p;
	}
	int p=0;
	for (int i=1;i<=n;i++)
	{
		if (p) --p;
		int j=sa[rk[i]-1];
		while (s[i+p]==s[j+p]) ++p;
		ht[rk[i]]=p;
	}
}
int a[MAXN],p[MAXN];
int fa[MAXN];
int find(const int& x){return fa[x]==x? x:fa[x]=find(fa[x]);}
ll mx[MAXN],sc[MAXN],mn[MAXN],cs[MAXN],siz[MAXN];
inline bool cmp(const int& a,const int& b){return ht[a]>ht[b];}
ll ans[MAXN][2],tmp[2];
int main()
{
	scanf("%d",&n);
	scanf("%s",s+1);
	SA();
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++) siz[i]=1,fa[i]=p[i]=i,mx[i]=mn[i]=a[sa[i]],sc[i]=-INF,cs[i]=INF;
	sort(p+1,p+n+1,cmp);
	int cur=n-1;
	tmp[1]=-INF;
	for (int i=1;i<=n;i++)
	{
		int x=find(p[i]),y=find(p[i]-1);
		tmp[0]+=siz[x]*siz[y];
		siz[x]+=siz[y];
		if (mx[y]>mx[x]) sc[x]=max(mx[x],sc[y]),mx[x]=mx[y];
		else if (mx[y]>sc[x]) sc[x]=mx[y];
		if (mn[y]<mn[x]) cs[x]=min(mn[x],cs[y]),mn[x]=mn[y];
		else if (mn[y]<cs[x]) cs[x]=mn[y];
		tmp[1]=max(tmp[1],max(mx[x]*sc[x],mn[x]*cs[x]));
		fa[y]=x;
		ans[ht[p[i]]][0]=tmp[0],ans[ht[p[i]]][1]=tmp[1];
	}
	for (int i=n-1;i>=0;i--) if (ans[i][0]==0&&ans[i][1]==-INF) ans[i][0]=ans[i+1][0],ans[i][1]=ans[i+1][1];
	for (int i=0;i<=n-1;i++) printf("%lld %lld\n",ans[i][0],ans[i][1]==-INF? 0:ans[i][1]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值