题解:CodeForce 1300E Subset Trick

1. Description

我们称一个数 \(x\) 对于一个集合 \(S\) 是不合适的,当且仅当存在两个大小相同的子集,满足其中一个子集的子集和 \(\le x\),而另一个子集的子集和 \(>x\)
现在给出一个集合 \(S\) 和若干次增加,删除操作,询问刚开始以及每一次操作后,有多少个数 \(x\) 对现在的集合是不合适的。

2. Solution

首先是一个很显然的结论,如果令前 \(i\) 小的数的和为 \(mi_i\),前 \(i\) 大的数的和为 \(mx_i\),则不合适的 \(x\) 组成的集合是 \(\cup _{i=1}^{|S|} [mi_i,mx_i)\)
这是很显然的,这里不作证明。
很显然也会发现,所有区间要么相交,要么相离,不会出现包含的情况,因为显然会有 \(mi_i\ge mi_{i-1},mx_i\ge mx_{i-1}\)
此时存在结论:相交的区间对应的下标是一段连续的区间。
我们考虑证明这个结论。

proof

首先,两个区间相交,当且仅当 \(mi_i\le mx_{i-1}\)
假定存在两个区间 \([l_1,r_1],[l_2,r_2]\) 满足 \(l_2<r_1\)\(\forall i\in[l_1+1,r_1],mi_i\le mx_{i-1}\)\(\forall i\in[l_2+1,r_2],mi_i\le mx_{i-1}\)
则有 \(mi_{r_1}\le mx_{r_1-1}\)\(mi_{r_1+1}>mx_{r_1}\),所以 \(mi_{r_1+1}-mi_{r_1}> mx_{r_1}-mx_{r_1-1}\),也就是第 \(r_1+1\) 小的数比第 \(r_1\) 大的数大,而因为 \(mi_{l_2}>mx_{l_2-1},mi_{l_2+1}\le mx_{l_2}\),也就是第 \(l_2+1\) 小的数比第 \(l_2\) 大的数小,这显然是不可能出现的,因为第 \(r_1+1\) 小的数小于第 \(l_2+1\) 小的数,而第 \(r_1\) 大的数大于第 \(l_2\) 大的数。

所以 \([1,n]\) 会被分成三段,第一段 \(mi_i>mx_{i-1}\) 且第 \(i\) 小的数小于第 \(i-1\) 大的数,第二段 \(mi_i\le mx_{i-1}\),第三段 \(mi_i>mx_{i-1}\) 且第 \(i\) 小的数大于第 \(i-1\) 大的数。

显然可以使用二分找到分界点 \([l,r]\),表示 \([l,r]\) 这一段的区间相交,其余区间相离。
然后我们需要求出 \(\sum_{i=1}^{l-1} mx_i-mi_i+\sum_{i=r+1}^{|S|} mx_i-mi_i+mx_{r}-mi_{l}\)
这个东西可以通过维护一棵平衡树来维护,在每一个节点维护区间内的数字个数,区间和,区间前缀和之和和区间后缀和之和。
当然,更进一步的,我们会发现 \(r=n-l\),同样尝试着证明这个结论。
显然有关系 \(mi_i=sum-mx_{n-i}\)
题设条件 \(mi_i \le mx_{i-1}\) 可改写为:\(mi_i\le S-mi_{n-i+1}\)
我们定义 \(f(i)=mi_i+mi_{n-i+1}\),也就是 \(f(i)-S\le 0\),很显然的,\(f(i)=f(n-i+1)\)
所以不难得到极大区间 \([l,r]\) 就是 \(\forall i\in(l,r],f(i)-S\le 0\),又因为存在对称性,所以 \(l+1=n-r+1\),所以 \(l+r=n\)

3. Code

/*by ChenMuJiu*/
/*略去缺省源与快读快写*/
const int N=2e5+5;
int n,m;
mt19937 ran(Name);
struct Treap{
	#define Siz(x) (x==0?0:f[x].siz)
	#define Sum(x) (x==0?0:f[x].sum)
	int rt,num;
	int ch[N<<1][2];
	ll val[N<<1];
	struct Node{
		int siz;
		ll sum,sumpre;
		Node(int _siz=0,ll _sum=0,ll _sumpre=0){
			siz=_siz;
			sum=_sum,sumpre=_sumpre;
		}
		Node operator +(const Node &T)const{
			return Node(siz+T.siz,sum+T.sum,sumpre+T.sumpre+T.siz*sum);
		}
	}f[N<<1];
	unsigned int c[N<<1];
	int New(ll _v=0){
		int p=++num;
		ch[p][0]=0,ch[p][1]=0;
		val[p]=_v;
		f[p]=Node(1,val[p],val[p]);
		c[p]=ran();
		return p;
	}
	void pushup(int p){
		f[p]=Node(1,val[p],val[p]);
		if(ch[p][0])f[p]=f[ch[p][0]]+f[p];
		if(ch[p][1])f[p]=f[p]+f[ch[p][1]];
	}
	int merge(int x,int y){
		if(x==0&&y==0)return 0;
		if(x==0)return y;
		if(y==0)return x;
		if(c[x]<c[y]){
			ch[x][1]=merge(ch[x][1],y);
			pushup(x);
			return x;
		}else{
			ch[y][0]=merge(x,ch[y][0]);
			pushup(y);
			return y;
		}
	}
	pii split_val(int p,ll v){
		if(!p)return {0,0};
		if(val[p]<=v){
			pii tmp=split_val(ch[p][1],v);
			ch[p][1]=tmp.first;
			pushup(p);
			return {p,tmp.second};
		}else{
			pii tmp=split_val(ch[p][0],v);
			ch[p][0]=tmp.second;
			pushup(p);
			return {tmp.first,p};
		}
	}
	pii split_siz(int p,int sz){
		if(!p)return {0,0};
		if(Siz(ch[p][0])>=sz){
			pii tmp=split_siz(ch[p][0],sz);
			ch[p][0]=tmp.second;
			pushup(p);
			return {tmp.first,p};
		}else{
			pii tmp=split_siz(ch[p][1],sz-Siz(ch[p][0])-1);
			ch[p][1]=tmp.first;
			pushup(p);
			return {p,tmp.second};
		}
	}
	void insert(ll v){
		pii tmp=split_val(rt,v-1);
		rt=merge(tmp.first,merge(New(v),tmp.second));
		return ;
	}
	void erase(ll v){
		pii tmp1=split_val(rt,v-1);
		pii tmp2=split_val(tmp1.second,v);
		rt=merge(tmp1.first,tmp2.second);
	}
	pair<ll,ll> query(int p,int k){
		if(k<=Siz(ch[p][0]))return query(ch[p][0],k);
		k-=Siz(ch[p][0]);
		if(k<=1)return {Sum(ch[p][0])+val[p],val[p]};
		k--;
		pair<ll,ll> tmp=query(ch[p][1],k);
		return {Sum(ch[p][0])+val[p]+tmp.first,tmp.second};
	}
	pair<ll,ll> querymi(int k){
		return query(rt,k);
	}
	pair<ll,ll> querymx(int k){
		pair<ll,ll> tmp=query(rt,f[rt].siz-k+1);
		return {f[rt].sum-tmp.first+tmp.second,tmp.second};
	}
	ll __querypresum(int p,int k){
		if(k<=Siz(ch[p][0]))return __querypresum(ch[p][0],k);
		ll res=0;
		k-=Siz(ch[p][0]);
		res+=ch[p][0]?f[ch[p][0]].sumpre+k*f[ch[p][0]].sum:0;
		if(k<=1)return res+val[p];
		k--;
		res+=val[p]*(k+1);
		return res+__querypresum(ch[p][1],k);
	}
	ll querypresum(int l,int r){
		if(l>r)return 0;
		ll res;
		if(r==n)res=f[rt].sumpre;
		else res=__querypresum(rt,r);
		if(l!=1)res-=__querypresum(rt,l-1);
		return res;
	}
	int size(){
		return Siz(rt);
	}
	ll querysum(int L){
		int n=f[rt].siz;
		ll sum1=querypresum(1,L);
		ll sum2=querypresum(n-L+1,n);
		return 1ll*(2*L+1)*f[rt].sum-2ll*sum1-2ll*sum2;
	}
}treap;
ll query(){
	int n=treap.size(); 
	if(n<=1)return 0;
	int l,r,L,R;
	l=2,r=n-1,L=n;
	while(l<=r){
		int mid=l+r>>1;
		pair<ll,ll> pre=treap.querymi(mid);
		pair<ll,ll> suf=treap.querymx(mid-1);
		if(pre.first<=suf.first){
			L=mid;
			r=mid-1;
		}else if(pre.second<suf.second)l=mid+1;
		else r=mid-1;
	} 
	if(L==n)return 1ll*(n+1)*treap.f[treap.rt].sum-2ll*treap.f[treap.rt].sumpre;
	L--;
	R=n-L;
	return treap.querysum(L);
}
signed main(){
	read(n),read(m);
	for(int i=1;i<=n;i++){
		ll x;
		read(x);
		treap.insert(x);
	}
	write(query()),Nxt;
	for(int i=1,opt;i<=m;i++){
		ll x;
		read(opt),read(x);
		if(opt==1)treap.insert(x);
		else treap.erase(x);
		write(query()),Nxt;
	}
}
posted @ 2026-05-14 14:02  陈牧九  阅读(9)  评论(0)    收藏  举报