NOI 2015 品酒大会

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

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

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

那么此时我们就可以使用若干个数组A1,A2,...去维护这些数对,保证对于i,jAp,ij,满足(i,j)r相似的。比如,样例1中,r=1时,我们就有(1,8),(2,4,9),(5,6,7,10),(3)这四个数组。并且方案数和是容易计算的,即为|Ai|×(|Ai|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)这七个数组了;而r3,就会有(1),(2),...,(10)10个数组。

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

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

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

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

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

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

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

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

代码:

#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 @   Nastia  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示