[CF1500E] Subset Trick (平衡树)

题面

洛谷翻译

V a n y a \rm Vanya Vanya 有一个初始大小为 n n n 的集合 S S S q q q 次往集合加数/删数的操作。(集合中每个数字不同)

称一个数 x x x 为不合适的,当前仅当满足可以取出 S S S 的两个大小相同的子集,其中一个元素和 ≤ x \le x x,另一个元素和 > x >x >x

求初始集合与每次操作结束后的集合不合适 x x x 的数量

n , q ≤ 2 e 5 , S i ≤ 1 e 13 n,q\leq 2e5,S_i\leq 1e13 n,q2e5,Si1e13

3000 ms , 512 mb

题解

洛谷的翻译已经转换了一部分题意了,我们接着转换。

既然是两个大小相同的子集,那么针对每种子集大小,我们不妨找出元素和最小以及元素和最大的两个集合,令其元素和分别为 L , R L,R L,R,那么 [ L , R ) [L,R) [L,R) 以内都是不合适的 x x x

我们把整个集合从小到大排成一列,会发现问题简单了许多。元素和最小的大小为 i i i 的集合就是前缀 i i i s u m ( i ) sum(i) sum(i)),同理,元素和最大的大小为 i i i 的集合就是后缀 ∣ S ∣ − i + 1 |S|-i+1 Si+1 s u f ( ∣ S ∣ − i + 1 ) suf(|S|-i+1) suf(Si+1))。

回过头来,我们可以把要求的答案表示为 [ L 1 , R 1 ) ∪ [ L 2 , R 2 ) ∪ . . . ∪ [ L ∣ S ∣ , R ∣ S ∣ ) [L_1,R_1)\cup[L_2,R_2)\cup...\cup[L_{|S|},R_{|S|}) [L1,R1)[L2,R2)...[LS,RS),即 [ s u m ( 1 ) , s u f ( ∣ S ∣ ) ) ∪ [ s u m ( 2 ) , s u f ( ∣ S ∣ − 1 ) ) ∪ . . . ∪ [ s u m ( ∣ S ∣ ) , s u f ( 1 ) ) [sum(1),suf(|S|))\cup[sum(2),suf(|S|-1))\cup...\cup[sum(|S|),suf(1)) [sum(1),suf(S))[sum(2),suf(S1))...[sum(S),suf(1)) ,我们会发现一些规律:

  1. 由于序列是单增的,因此 y = s u m ( x ) y=sum(x) y=sum(x) 下凸, y = s u f ( ∣ S ∣ − x + 1 ) y=suf(|S|-x+1) y=suf(Sx+1) 上凸。
  2. s u m ( ∣ S ∣ ) = s u f ( 1 ) sum(|S|)=suf(1) sum(S)=suf(1) ,因此 [ s u m ( ∣ S ∣ ) , s u f ( 1 ) ) [sum(|S|),suf(1)) [sum(S),suf(1)) 是空集,我们把它去掉。
  3. s u m ( i ) = S u m S − s u f ( i + 1 ) , s u f ( ∣ S ∣ − i + 1 ) = S u m S − s u m ( ∣ S ∣ − i ) sum(i)={\rm Sum_S}-suf(i+1),suf(|S|-i+1)={\rm Sum_S}-sum(|S|-i) sum(i)=SumSsuf(i+1),suf(Si+1)=SumSsum(Si) ,因此 [ L i , R i ] [L_i,R_i] [Li,Ri] [ L ∣ S ∣ − i , R ∣ S ∣ − i ] [L_{|S|-i},R_{|S|-i}] [LSi,RSi] 是大小相等的,整个并集是左右对称的。

我们会发现,难点就在于这是集合并,不能简单地求长度和,我们得判断哪些区间融合了,哪些单了出来。好在之前的规律可以帮助我们,我们可以发现一个规律:整个并集两边的区间单出来一些(possibly, none),中间则是一个大区间,也就是存在中间一段 [ L s , R s ) ∪ . . . ∪ [ L t , R t ) [L_s,R_s)\cup...\cup[L_t,R_t) [Ls,Rs)...[Lt,Rt) 是一整个大区间,其余的 { [ L i , R i ) ∣ i < s ∨ i > t } \{[L_i,R_i) |i<s\vee i>t\} {[Li,Ri)i<si>t} 都是独立的(长度可以直接加)。形似这样:

[)  [-)    [---)     [-----------------------------------)     [---)    [-)  [)
  • 证明:由于并集对称,我们只证明左半边,右半边同理。考虑左半边某个区间 [ s u m ( i ) , s u f ( ∣ S ∣ − i + 1 ) ) [sum(i),suf(|S|-i+1)) [sum(i),suf(Si+1)) 是独立的,那么 s u f ( ∣ S ∣ − i + 1 ) < s u m ( i + 1 ) suf(|S|-i+1)<sum(i+1) suf(Si+1)<sum(i+1) ,也即
    s u f ( ∣ S ∣ − ( i − 1 ) + 1 ) + S ∣ S ∣ − i + 1 < s u m ( i ) + S i + 1 (1) suf(|S|-(i-1)+1)+S_{|S|-i+1}<sum(i)+S_{i+1}\tag{1} suf(S(i1)+1)+SSi+1<sum(i)+Si+1(1)
    由于是在序列左半边,序列从左到右单增,因此
    S i + 1 < S ∣ S ∣ − i + 1    ⇔    − S ∣ S ∣ − i + 1 < − S i + 1 (2) S_{i+1}<S_{|S|-i+1}\;\Leftrightarrow\;-S_{|S|-i+1}<-S_{i+1}\tag{2} Si+1<SSi+1SSi+1<Si+1(2)
    由于同号,因此把这个不等式 ( 2 ) (2) (2) 和上面的不等式 ( 1 ) (1) (1) 左右两边分别相加,可得:
    s u f ( ∣ S ∣ − ( i − 1 ) + 1 ) < s u m ( ( i − 1 ) + 1 ) suf(|S|-(i-1)+1)<sum((i-1)+1) suf(S(i1)+1)<sum((i1)+1)
    所以我们发现区间 [ L i − 1 , R i − 1 ) = [ s u m ( i − 1 ) , s u f ( ∣ S ∣ − ( i − 1 ) + 1 ) ) [L_{i-1},R_{i-1})=[sum(i-1),suf(|S|-(i-1)+1)) [Li1,Ri1)=[sum(i1),suf(S(i1)+1)) 也就是它左边那个区间也是独立的。
    由此可得,左边的独立区间一定是最靠左的连续一段 [ L i , R i ) [L_i,R_i) [Li,Ri) ,由于对称,右边也一样。证毕。

我们每次计算答案可以只算左半边,就能得到右半边的答案,同时要处理最中间的特殊情况。我们先考虑求区间独立的范围 [ 1 , s ) [1,s) [1,s) ,这个可以二分位置,然后判断 s u m sum sum s u f suf suf 的大小关系。我们得到 s s s 过后, t t t 就可以直接对称得到,中间的大区间就很好计算了,我们找到右端点左端点做个减法就行。对于左边的独立区间,答案就是
∑ i = 1 s − 1 ( R i − L i ) = ∑ i = 1 s − 1 s u f ( ∣ S ∣ − i + 1 ) − ∑ i = 1 s − 1 s u m ( i ) = ∑ i = 1 s − 1 ( s − i ) S ∣ S ∣ − i + 1 − ∑ i = 1 s − 1 ( s − i ) S i \sum_{i=1}^{s-1} (R_i-L_i)=\sum_{i=1}^{s-1} suf(|S|-i+1)-\sum_{i=1}^{s-1} sum(i)\\ =\sum_{i=1}^{s-1}(s-i)S_{|S|-i+1}-\sum_{i=1}^{s-1}(s-i)S_{i} i=1s1(RiLi)=i=1s1suf(Si+1)i=1s1sum(i)=i=1s1(si)SSi+1i=1s1(si)Si

综上所述,我们得用数据结构维护一个单增序列每段子串的 ∑ S i \sum S_i Si ∑ S i ⋅ i \sum S_i\cdot i Sii ,以及同时,该数据结构还得支持

  • 从某个值后面添加一个数
  • 删除某个值的数

那么最适合的数据结构就是平衡树了!虽然有点卡常。

官解做法:

推理结论都差不多,只不过角度不同:官解是用全集 - 空隙,然后证明了最左和最右两端的空隙都是连续的(定义了 f ( k ) = s u m ( k + 1 ) − s u f ( n − k + 1 ) f(k)=sum(k+1)-suf(n-k+1) f(k)=sum(k+1)suf(nk+1),然后直接告诉你它在左半边是单减的(Let’s note two simply things: …))。

然后也是二分边界,维护元素和以及元素带权和,只不过用的是离散化+线段树,常数小一些,但是自我感觉想起来很麻烦。

CODE

无旋Treap ,中间求sumsuf时卡了卡常。
无O2: 2979 ms ,加O2: 2542 ms

#include<map>
#include<queue>
#include<ctime>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
#define eps 1e-9
//#pragma GCC optimize(2)
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
LL a[MAXN];
//----------------------------------Treap
struct np{int s[2];np(){s[0]=s[1]=0;}np(int A,int B){s[0]=A;s[1]=B;}};
struct tr{
	int s[2],siz,hp,lz;
	LL ky,sm,k2,sm2;
	tr(){s[0]=s[1]=siz=hp=0;ky=sm=k2=sm2=lz=0;}
}tre[MAXN<<1];
int CNT;
inline int newnode(LL key) {
	int x = ++ CNT; tre[x] = tr();
	tre[x].ky = tre[x].sm = tre[x].k2 = tre[x].sm2 = key;
	tre[x].siz = 1; tre[x].hp = rand() *1ll* rand() % 998244353;
	return x;
}
inline int update(int x) {
	int ls = tre[x].s[0],rs = tre[x].s[1];
	tre[x].sm = tre[x].ky + tre[ls].sm + tre[rs].sm;
	tre[x].sm2 = tre[x].k2 + tre[ls].sm2 + tre[rs].sm2 + (tre[ls].sm + tre[rs].sm) *1ll* tre[x].lz;
	tre[x].siz = tre[ls].siz + tre[rs].siz + 1;
	tre[0] = tr(); return x;
}
inline void addtm(int x,int y) {
	if(!x) return ;
	tre[x].k2 += tre[x].ky *1ll* y;
	tre[x].sm2 += tre[x].sm *1ll* y;
	tre[x].lz += y; return ;
}
inline void pushdown(int x) {
	if(!x) return ;
	int ls = tre[x].s[0],rs = tre[x].s[1];
	if(tre[x].lz) {
		addtm(ls,tre[x].lz); addtm(rs,tre[x].lz);
		tre[x].lz = 0;
	}return ;
}
inline np spli1(int x,LL k) {
	np as(0,0); if(!x) return as;
	pushdown(x);
	int d = (tre[x].ky <= k ? 1:0);
	as = spli1(tre[x].s[d],k);
	tre[x].s[d] = as.s[d^1];
	as.s[d^1] = update(x); return as;
}
inline np spli2(int x,int rk) {
	np as(0,0); if(!x) return as;
	pushdown(x); if(rk <= 0) return np(0,x);
	int d = (tre[tre[x].s[0]].siz+1 <= rk ? 1:0);
	if(d) rk -= tre[tre[x].s[0]].siz+1;
	as = spli2(tre[x].s[d],rk);
	tre[x].s[d] = as.s[d^1];
	as.s[d^1] = update(x); return as;
}
inline int merg(int p1,int p2) {
	if(!p1 || !p2) return p1+p2;
	pushdown(p1); pushdown(p2);
	if(tre[p1].hp < tre[p2].hp) {tre[p1].s[1] = merg(tre[p1].s[1],p2);return update(p1);}
	tre[p2].s[0] = merg(p1,tre[p2].s[0]); return update(p2);
}
inline int ins(int x,int y) {
	np p = spli1(x,tre[y].ky);
	addtm(y,tre[p.s[0]].siz); addtm(p.s[1],1);
	return merg(merg(p.s[0],y),p.s[1]);
}
inline int del(int x,LL k) {
	np p1 = spli1(x,k-1);
	np p2 = spli2(p1.s[1],1);
	if(p2.s[0] && tre[p2.s[0]].ky == k) p2.s[0] = 0,addtm(p2.s[1],-1);
	return merg(p1.s[0],merg(p2.s[0],p2.s[1]));
}
inline LL sum(int x,int y) {
	if(!x || !y) return 0ll;
	if(tre[tre[x].s[0]].siz+1 <= y)
		return tre[tre[x].s[0]].sm+tre[x].ky+sum(tre[x].s[1],y-(tre[tre[x].s[0]].siz+1));
	return sum(tre[x].s[0],y);
}
inline LL suf(int x,int y) {
	if(!x || y > tre[x].siz) return 0ll;
	if(tre[tre[x].s[0]].siz+1 < y)
		return suf(tre[x].s[1],y-(tre[tre[x].s[0]].siz+1));
	return tre[tre[x].s[1]].sm+tre[x].ky+suf(tre[x].s[0],y);
}
inline LL sum2(int &x,int y) {
	np p1 = spli2(x,y);
	LL res = tre[p1.s[0]].sm * (1ll+y) - tre[p1.s[0]].sm2;
	x = merg(p1.s[0],p1.s[1]);
	return res;
}
inline LL suf2(int &x,int y) {
	np p1 = spli2(x,y-1);
	LL res = tre[p1.s[1]].sm2 - tre[p1.s[1]].sm *1ll* (y-1ll);
	x = merg(p1.s[0],p1.s[1]);
	return res;
}
// ----------------------------------------------------------------
int root = 0;
inline LL calcu() {
	n = tre[root].siz;
	if(!root || n <= 0) return 0ll;
	int md = n/2,st = 0;
	LL ans = 0;
	for(int i = 19;i >= 0;i --) {
		int ad = st+(1<<i);
		if(ad < md && suf(root,n-ad+1) < sum(root,ad+1)) st = ad;
	}
	if(st) ans += (suf2(root,n-st+1) - sum2(root,st)) * 2ll;
	if((n-1) & 1) {
		int rr = 2*md-st-1;
		LL l1 = sum(root,st+1), r1 = suf(root,n-rr+1);
		ans += r1 - l1;
	}
	else if(n > 1) {
		int rr = 2*md-st;
		LL l1 = sum(root,st+1),r1 = suf(root,n-md+1);
		LL l2 = sum(root,md+1),r2 = suf(root,n-rr+1);
		if(r1 < l2) ans += r1-l1 + r2-l2;
		else ans += r2 - l1;
	}
	return ans;
}
int main() {
	n = read(); m = read();
	for(int i = 1;i <= n;i ++) {
		a[i] = read();
		root = ins(root,newnode(a[i]));
	}
	printf("%lld\n",calcu());
	for(int i = 1;i <= m;i ++) {
		o = read();
		LL nm = read();
		if(o == 1) root = ins(root,newnode(nm));
		else root = del(root,nm);
		printf("%lld\n",calcu());
	}
	return 0;
}

官解线段树jly的代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值