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

传送门

传送门

题意:给一个长度为NNN的字符串和一个长度为NNN的序列AAA,对于所有的k[0,N1]k \in [0,N-1]k∈[0,N−1],求选出两个数i,ji,ji,j满足lcp(suffix(i),suffix(j))=klcp(suffix(i),suffix(j))=klcp(suffix(i),suffix(j))=k的方案数和Ai×AjA_i \times A_jAi​×Aj​的最大值

数据范围:暴力跑不过

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

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

显然求出heightheightheight数组,倒着排序

显然这是单调的

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

显然可以用并查集

对于方案数,维护一个sizesizesize ,合并的时候相乘再计入答案

对于Ai×AjA_i \times A_jAi​×Aj​的最大值,维护当前AAA的最大值和次大值

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

瞎维护一通,信仰ACACAC

#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;
}
上一篇:转一个后缀数组的简单总结:


下一篇:后缀数组总结