NOI 2015 品酒大会

这题是SA+DSU的一道好题(我一开始想用FHQ Treap,不过DSU就可以维护,两种做法长度应该差不多)

首先我们要解决的第一个问题就是,数对太多(\(O(n^2)\)级别),我们没法使用任何东西去维护它。

观察样例\(1\)\((2,4),(2,9),(4,9)\)都是\(1\)相似的。从而我们发现一个重要的性质——如果\((a,b)\)\(r\)相似的,\((b,c)\)\(r\)相似的,那么\((a,c)\)也是\(r\)相似的。

那么此时我们就可以使用若干个数组\(A_{1},A_{2},...\)去维护这些数对,保证对于\(i,j\in A_{p},i\neq j\),满足\((i,j)\)\(r\)相似的。比如,样例\(1\)中,\(r=1\)时,我们就有\((1,8),(2,4,9),(5,6,7,10),(3)\)这四个数组。并且方案数和是容易计算的,即为\(\sum \frac{|A_{i}|\times (|A_{i}|-1)}{2}\);最大乘积也是可以计算的,要取每个数组的前\(2\)大或前\(2\)小(负负得正)的值相乘即可。

此外,我们还可以发现,每当\(r\)增加时,一个数组会逐渐分裂成多个数组,还是样例\(1\)的例子,比如\(r=0\)时,只有一个数组\((1,2,3,...,10)\);当\(r=1\)时就是\((1,8),(2,4,9),(5,6,7,10),(3)\)这四个数组;当\(r=2\)时就是\((1,8),(4,9),(5,6),(2),(3),(7),(8)\)这七个数组了;而\(r\geq 3\),就会有\((1),(2),...,(10)\)\(10\)个数组。

但是我们不好维护一个数组的分裂(因为一个数组可能会分裂成若干个数组),所以我们倒过来考虑这个问题,此时这个问题转成:

给你\(n\)个数组,第\(i\)个数组初始只有一个数\(i\),之后如果一个时刻\(r\),满足\((i,j)\)\(r\)相似的,并且此时它们在不同的数组中,我们合并它们。
并且,我们需要维护数组的大小、数组前\(2\)大和前\(2\)小的值。

维护数组的大小、数组前\(2\)大和前\(2\)小的值,这个可以用并查集+set或FHQ Treap实现,由于一共只要合并\(O(n)\)次,所以时间复杂度就是\(O(n\log n)\)

但难点在于我们怎么知道何时要合并两个数组。

我们可以考虑最早合并两个数,肯定是它们相同的连续段最长。那么如何知道 与某个后缀的 公共前缀 最长的 后缀 呢?肯定是后缀排序后的 前一项 或 后一项 啊!
(这里进行了人工断句)

所以我们将这个字符串进行后缀排序。那么两两之间合并的时间,一定是两个字符串间最早合并的时间,比如\((sa_i,sa_j)\)要在\(r\)时刻合并,且\(i,j\)不相邻,那么此时\((sa_i,sa_{i+1}),(sa_{i+1},sa_{i+2}),...,(sa_{j-1},sa_j)\)一定已经合并过了,所以我们只需关心相邻两项(指后缀排序过后相邻的)合并的时间即可。这个时间就是\(LCP(sa_i,sa_{i+1})=height_{i+1}\)

所以,我们最终的算法,就是后缀排序,求出\(height\)数组,然后从\(n\)\(0\)枚举时刻\(r\),看此时我们要将哪两个相邻的后缀(指后缀排序过后相邻的)合并,维护合并前后的数组即可。

\(height\)数组是\(O(n)\)的,后缀排序是\(O(n\log n)\)的,故总时间复杂度为\(O(n\log n)\)

代码:

#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl

const int maxn=300005;
int n,m=122;
int a[maxn],c[maxn],x[maxn],y[maxn],sa[maxn],height[maxn];
char s[maxn];
std::vector<int> v[maxn];
long long sum=0;
long long ans1[maxn],ans2[maxn];
std::multiset<long long> mul;

class DSU { public:
	int fa[maxn];
	std::vector<int> mn[maxn],mx[maxn];
	int root(int x) {
		if(fa[x]<0) return x;
		return fa[x]=root(fa[x]); 
	}
	void sub(std::vector<int> &v,std::vector<int> u,bool sev) {
		v.insert(v.end(),u.begin(),u.end());
		if(!sev) std::sort(v.begin(),v.end(),std::less<int>());
		else std::sort(v.begin(),v.end(),std::greater<int>());
		if((int)v.size()>2) v.resize(2);
	}
	long long calc(int ind) {
		long long ret=-1e18;
		if(mn[ind].size()>=2) ret=std::max(ret,1ll*mn[ind][0]*mn[ind][1]);
		if(mx[ind].size()>=2) ret=std::max(ret,1ll*mx[ind][0]*mx[ind][1]);
		return ret;
	}
	void merge(int x,int y) {
		int px=root(x),py=root(y);
		if(px==py) return;
		if(fa[px]>fa[py]) std::swap(px,py);
		fa[px]+=fa[py]; fa[py]=px;
		long long rec=calc(px);
		if(mul.find(rec)!=mul.end()) mul.erase(mul.find(rec));
		rec=calc(py);
		if(mul.find(rec)!=mul.end()) mul.erase(mul.find(rec));
		sub(mn[px],mn[py],0); sub(mx[px],mx[py],1);
		rec=calc(px);
		mul.insert(rec);
	}
	int size(int x) {
		x=root(x); return -fa[x];
	}
}solver;

#define ways(x) (((long long)(x))*((long long)(x)-1ll)/2ll) 

int main() {
	scanf("%d",&n); scanf("%s",s+1);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) c[x[i]=s[i]]++;
	for(int i=1;i<=m;i++) c[i]+=c[i-1];
	for(int i=n;i>=1;i--) sa[c[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1) {
		int ptr=0;
		for(int i=n-k+1;i<=n;i++) y[++ptr]=i;
		for(int i=1;i<=n;i++) if(sa[i]>k) y[++ptr]=sa[i]-k;
		memset(c,0,sizeof c);
		for(int i=1;i<=n;i++) c[x[i]]++;
		for(int i=1;i<=m;i++) c[i]+=c[i-1];
		for(int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
		std::swap(x,y);
		x[sa[1]]=1,ptr=1;
		for(int i=2;i<=n;i++) {
			if(y[sa[i]]!=y[sa[i-1]]||y[sa[i]+k]!=y[sa[i-1]+k]) ptr++;
			x[sa[i]]=ptr;
		}
		if(ptr==n) break;
		m=ptr;
	}
	for(int i=1,k=0;i<=n;i++) {
		if(!x[i]) continue;
		if(k) k--;
		while(s[i+k]==s[sa[x[i]-1]+k]) k++;
		height[x[i]]=k;
	}
	for(int i=2;i<=n;i++)
		v[height[i]].push_back(i);
	for(int i=1;i<=n;i++) {
		solver.fa[i]=-1;
		solver.mn[i].push_back(a[sa[i]]);
		solver.mx[i].push_back(a[sa[i]]); 
	}
	for(int i=n;i>=0;i--) {
		for(int j=0;j<(int)v[i].size();j++) {
			int y=v[i][j],x=y-1;
			sum-=ways(solver.size(x)); sum-=ways(solver.size(y));
			solver.merge(x,y);
			sum+=ways(solver.size(x));
		}
		ans1[i]=sum; if(!mul.empty()) ans2[i]=*mul.rbegin();
	}
	for(int i=0;i<n;i++) {
		if(ans1[i]==0) printf("0 0\n");
		else printf("%lld %lld\n",ans1[i],ans2[i]);
	}
	return 0;
}
posted @ 2022-06-22 13:52  Nastia  阅读(28)  评论(0)    收藏  举报