[NOI2015]品酒大会

[NOI2015]品酒大会

题目大意:

一个长度为\(n(n\le3\times10^5)\)的字符串\(s(s_i\in[\text{'a'},'z'])\),若对于后缀\(i,j\)\(\operatorname{lcp}(i,j)\le r\),则我们称后缀对\((i,j)\)为「\(r\)相似」的。另有数列\(a_{1\sim n}\),表示后缀对\((i,j)\)的贡献为\(a_i\cdot a_j\)。对于\(r=0,1,\ldots,n-1\),求「\(r\)相似」的后缀对有多少组,其中贡献的最大值为多少。

思路:

首先用后缀数组求出字符串\(s\)\(lcp\)数组。对于\(lcp\)数组排序,按从大到小顺序合并\(lcp=i\)对应的两个后缀所属的联通块,合并后块内任意两个后缀的\(\operatorname{lcp}\)一定\(\ge r\),即一定为「\(r\)相似」的。对于每个连通块,维护\(a_i\)的最大值、次大值、最小值、次小值即可求得两数之积的最大值。对于方案数,可以简单地求出合并后各连通块内两两组合的方案数之和。

使用\(O(n\log^2n)\)的后缀数组可以在洛谷、LOJ、UOJ上通过,BZOJ会被卡,需要使用更优秀的算法。

源代码:

#include<cstdio>
#include<cctype>
#include<vector>
#include<climits>
#include<algorithm>
#include<functional>
typedef long long int64;
inline int getint() {
	register char ch;
	register bool neg=false;
	while(!isdigit(ch=getchar())) neg|=ch=='-';
	register int x=ch^'0';
	while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0');
	return neg?-x:x;
}
const int N=3e5+1;
char s[N];
int n,k,sa[N],rank[N],tmp[N];
inline bool cmp(const int &i,const int &j) {
	if(rank[i]!=rank[j]) return rank[i]<rank[j];
	const int ri=i+k<=n?rank[i+k]:-1;
	const int rj=j+k<=n?rank[j+k]:-1;
	return ri<rj;
}
inline void suffix_sort() {
	for(register int i=0;i<=n;i++) {
		sa[i]=i;
		rank[i]=s[i];
	}
	for(k=1;k<=n;k<<=1) {
		std::sort(&sa[0],&sa[n]+1,cmp);
		tmp[sa[0]]=0;
		for(register int i=1;i<=n;i++) {
			tmp[sa[i]]=tmp[sa[i-1]]+cmp(sa[i-1],sa[i]);
		}
		std::copy(&tmp[0],&tmp[n]+1,rank);
	}
}
struct Node {
	int val,id;
	bool operator > (const Node &rhs) const {
		return val>rhs.val;
	}
};
Node lcp[N];
inline void init_lcp() {
	for(register int i=0,h=0;i<n;i++) {
		if(h>0) h--;
		const int &j=sa[rank[i]-1];
		while(i+h<n&&j+h<n&&s[i+h]==s[j+h]) h++;
		lcp[i]=(Node){h,i};
	}
}
int m[2][2][N];
int64 ans[N],cnt[N];
struct DisjointSet {
	int anc[N],size[N];
	void reset(const int &n) {
		for(register int i=1;i<=n;i++) {
			anc[i]=i;
			size[i]=1;
		}
	}
	int find(const int &x) {
		return x==anc[x]?x:anc[x]=find(anc[x]);
	}
};
DisjointSet djs;
int main() {
	n=getint();
	scanf("%s",s);
	for(register int i=1;i<=n;i++) {
		m[0][0][i]=m[1][0][i]=getint();
		m[0][1][i]=INT_MAX;
		m[1][1][i]=INT_MIN;
	}
	suffix_sort();
	init_lcp();
	std::sort(&lcp[0],&lcp[n],std::greater<Node>());
	djs.reset(n);
	ans[n]=LLONG_MIN;
	for(register int i=n-1,j=0;~i;i--) {
		cnt[i]=cnt[i+1];
		ans[i]=ans[i+1];
		for(;j<n&&lcp[j].val>=i;j++) {
			const int u=djs.find(lcp[j].id+1);
			const int v=djs.find(sa[rank[lcp[j].id]-1]+1);
			if(u==v||u==0||v==0) continue;
			djs.anc[u]=v;
			cnt[i]-=(int64)djs.size[u]*(djs.size[u]-1)/2+(int64)djs.size[v]*(djs.size[v]-1)/2;
			djs.size[v]+=djs.size[u];
			cnt[i]+=(int64)djs.size[v]*(djs.size[v]-1)/2;
			for(register int i=0;i<2;i++) {
				for(register int j=i;j<2;j++) {
					if(m[0][i][u]<m[0][j][v]) std::swap(m[0][i][u],m[0][j][v]);
					if(m[1][i][u]>m[1][j][v]) std::swap(m[1][i][u],m[1][j][v]);
				}
			}
			ans[i]=std::max(ans[i],std::max((int64)m[0][0][v]*m[0][1][v],(int64)m[1][0][v]*m[1][1][v]));
		}
	}
	for(register int i=0;i<n;i++) {
		printf("%lld %lld\n",cnt[i],ans[i]==LLONG_MIN?0:ans[i]);
	}
	return 0;
}
posted @ 2018-06-21 22:42  skylee03  阅读(122)  评论(0编辑  收藏  举报