CF::Gym 100960G - Youngling Tournament

CF::Gym题目页面传送门

给定一个数列\(a\),支持\(q\)次操作:令\(a_x=y\),并设\(a'\)\(a\)从大到小排序的结果,查询满足该位置上的数大于等于其后所有数的和的位置数量。

\(n\in\left[1,10^5\right],q\in\left[1,5\times10^4\right],a_i\in\left[1,10^{12}\right]\)

考虑设\(suM_i\)\(a'\)在位置\(i\)处的后缀和,那么位置\(i\)满足条件显然当且仅当\(a'_i-suM_{i+1}\geq0\)。不难想到维护\(a'_i-suM_{i+1}\)

肯定是要按\(a_i\)从大往小排列的。注意到每次修改,可能会让\(a_x\)\(a'\)中移个位置,而其他元素的相对位置不变。设\(a_x\)本来在\(a'\)中位置为\(p\),修改完跑到了\(p'\)。那么分\(p<p'\)\(p\geq p'\)两种情况。这里以前者为例,后者类似。

显然整个\(a'\)序列分成三段:

  1. \(1\sim p\),这一段的\(a'_i-suM_{i+1}\)值显然都要加上\(a_x-y\)
  2. \(p\sim p'\),这一段的\(a'_i-suM_{i+1}\)值显然都要加上\(-y\)
  3. \(p'\sim n\),这一段的\(a'_i-suM_{i+1}\)值显然不变。

看到区间增加,不难想到线段树配合懒标记。那么问题来了,维护啥呢?咋查询呢?线段树套平衡树肯定是不行的,因为是区间修改。线段树直接维护也维护不动。考虑让线段树起到剪枝的作用:每个节点维护当前区间的\(a'_i-suM_{i+1}\)最大值(这个显然是懒标记可做的)。查询的时候从根往下走,对于每个儿子,如果它的最大值\(\geq0\)则往下走,否则不往下走(即里面不可能有符合要求的位置)。

这样复杂度是多少呢?注意到一个非常重要的性质:答案是\(\mathrm O(\log v)\)级别的,其中\(v\)\(a\)的值域大小。证明非常简单,大概就是每有一个答案,后缀和就要增一倍,并且当前位置的数要大于等于后缀和。这样一来,每个符合要求的数就会有一条对应的线段树上的根到叶子的链,多条链的并集是被经过的节点集合,\(\mathrm O(\log n\log v)\)

考虑到还要插入与删除,想用线段树的话要离线预留好位置,比较烦我懒得写了。其他的方法有:动态开点线段树,\(\mathrm O\!\left(\log^2v\right)\);平衡树,复杂度不变,分析差不多。我写了后者,使用fhq-Treap。插入删除直接转化成修改,就不需要垃圾桶了。

时间复杂度\(\mathrm O(n\log n+q\log n\log v)\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define mp make_pair
#define X first
#define Y second
const int inf=0x3f3f3f3f3f3f3f3f;
mt19937 rng(20060617);
const int N=100000;
int n,qu;
int a[N+1];
int b[N+1],suM[N+2];
struct fhq_treap{ 
	int sz,root;
	struct node{unsigned key;int lson,rson,sz,v,dif,sum,mx,lz;}nd[N+1];
	#define key(p) nd[p].key
	#define lson(p) nd[p].lson
	#define rson(p) nd[p].rson
	#define sz(p) nd[p].sz
	#define v(p) nd[p].v
	#define dif(p) nd[p].dif
	#define sum(p) nd[p].sum
	#define mx(p) nd[p].mx
	#define lz(p) nd[p].lz
	int bld(int l=1,int r=n){
		int mid=l+r>>1,p=nwnd(b[mid],b[mid]-suM[mid+1]);
		if(l<mid)lson(p)=bld(l,mid-1);
		if(r>mid)rson(p)=bld(mid+1,r);
		return sprup(p),p;
	}
	void init(){
		sz=0;
		nd[0]=node({0,0,0,0,0,-inf,0,-inf,0});
		root=bld();
	}
	void sprup(int p){
		sum(p)=sum(lson(p))+v(p)+sum(rson(p));
		mx(p)=max(mx(lson(p)),max(dif(p),mx(rson(p))));
		sz(p)=sz(lson(p))+1+sz(rson(p));
	}
	void sprdwn(int p){
		if(lz(p)){
			tag(lson(p),lz(p));tag(rson(p),lz(p));
			lz(p)=0;
		}
	}
	void tag(int p,int v){
		if(p)dif(p)+=v,mx(p)+=v,lz(p)+=v;
	}
	pair<int,int> split(int x,int p=-1){~p||(p=root);
		if(!x)return mp(0,p);
		sprdwn(p);
		pair<int,int> sp;
		if(x<=sz(lson(p)))return sp=split(x,lson(p)),lson(p)=sp.Y,sprup(p),mp(sp.X,p);
		return sp=split(x-1-sz(lson(p)),rson(p)),rson(p)=sp.X,sprup(p),mp(p,sp.Y);
	}
	int mrg(int p,int q){
		if(!p||!q)return p|q;
		sprdwn(p);sprdwn(q);
		if(key(p)<key(q))return rson(p)=mrg(rson(p),q),sprup(p),p;
		return lson(q)=mrg(p,lson(q)),sprup(q),q;
	}
	int grt(int v,int p=-1){~p||(p=root);
		if(!p)return 0;
		sprdwn(p);
		if(v(p)>v)return sz(lson(p))+1+grt(v,rson(p));
		return grt(v,lson(p));
	}
	int nwnd(int v,int dif){
		return nd[++sz]=node({rng(),0,0,1,v,dif,v,dif,0}),sz;
	}
	void mv_rit(int v1,int v2){
		pair<int,int> sp=split(grt(v1)),sp0=split(1,sp.Y),sp1=split(grt(v2,sp0.Y),sp0.Y);
		//sp.X,del(sp0.X),sp1.X,insert(v2),sp1.Y
		v(sp0.X)=sum(sp0.X)=v2,dif(sp0.X)=mx(sp0.X)=v2-sum(sp1.Y);
		tag(sp.X,v1-v2);tag(sp1.X,-v2);
		root=mrg(sp.X,mrg(sp1.X,mrg(sp0.X,sp1.Y)));
	}
	void mv_lft(int v1,int v2){
		pair<int,int> sp=split(grt(v2)),sp0=split(grt(v1,sp.Y),sp.Y),sp1=split(1,sp0.Y);
		//sp.X,insert(v2),sp0.X,del(sp1.X),sp1.Y
		v(sp1.X)=sum(sp1.X)=v2,dif(sp1.X)=mx(sp1.X)=v2-sum(sp0.X)-sum(sp1.Y);
		tag(sp.X,v1-v2);tag(sp0.X,v1);
		root=mrg(sp.X,mrg(sp1.X,mrg(sp0.X,sp1.Y)));
	}
	int cnt(int p=-1){~p||(p=root);
		if(!p)return 0;
		sprdwn(p);
		int res=0;
		if(dif(p)>=0)res++;
		if(mx(lson(p))>=0)res+=cnt(lson(p));
		if(mx(rson(p))>=0)res+=cnt(rson(p));
		return res;
	}
	void dfs(int p=-1){~p||(p=root);
		if(!p)return;
		sprdwn(p);
		dfs(lson(p));
		cout<<v(p)<<" "<<dif(p)<<"!\n";
		dfs(rson(p));
	}
}trp;
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++)scanf("%lld",a+i),b[i]=a[i];
	sort(b+1,b+n+1,greater<int>());
	for(int i=n;i;i--)suM[i]=suM[i+1]+b[i];
	trp.init();
//	trp.dfs();
	cout<<trp.cnt()<<"\n";
	cin>>qu;
	while(qu--){
		int x,y;
		scanf("%lld%lld",&x,&y);
//		cout<<a[x]<<" "<<y<<"!!\n";
		if(a[x]>y)trp.mv_rit(a[x],y);
		else trp.mv_lft(a[x],y);
		a[x]=y;
//		trp.dfs();
		printf("%lld\n",trp.cnt());
	}
	return 0;
}

后来yxh告诉我了另一个神仙方法,是用二进制搞的,代码特别短。当时乍一看是1log的,woc这么强的吗???还准备学习一下。现在才发现也是2log的,复杂度跟上述做法一样,就懒得研究了。

总体来说是比较简单的一题。

posted @ 2020-08-08 15:33  ycx060617  阅读(182)  评论(2编辑  收藏  举报