[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,q≤2e5,Si≤1e13
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
∣S∣−i+1(
s
u
f
(
∣
S
∣
−
i
+
1
)
suf(|S|-i+1)
suf(∣S∣−i+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)∪...∪[L∣S∣,R∣S∣),即
[
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(∣S∣−1))∪...∪[sum(∣S∣),suf(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(∣S∣−x+1) 上凸。
- 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)) 是空集,我们把它去掉。
- 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)=SumS−suf(i+1),suf(∣S∣−i+1)=SumS−sum(∣S∣−i) ,因此
[
L
i
,
R
i
]
[L_i,R_i]
[Li,Ri] 和
[
L
∣
S
∣
−
i
,
R
∣
S
∣
−
i
]
[L_{|S|-i},R_{|S|-i}]
[L∣S∣−i,R∣S∣−i] 是大小相等的,整个并集是左右对称的。
我们会发现,难点就在于这是集合并,不能简单地求长度和,我们得判断哪些区间融合了,哪些单了出来。好在之前的规律可以帮助我们,我们可以发现一个规律:整个并集两边的区间单出来一些(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<s∨i>t} 都是独立的(长度可以直接加)。形似这样:
[) [-) [---) [-----------------------------------) [---) [-) [)
- 证明:由于并集对称,我们只证明左半边,右半边同理。考虑左半边某个区间
[
s
u
m
(
i
)
,
s
u
f
(
∣
S
∣
−
i
+
1
)
)
[sum(i),suf(|S|-i+1))
[sum(i),suf(∣S∣−i+1)) 是独立的,那么
s
u
f
(
∣
S
∣
−
i
+
1
)
<
s
u
m
(
i
+
1
)
suf(|S|-i+1)<sum(i+1)
suf(∣S∣−i+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∣−(i−1)+1)+S∣S∣−i+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<S∣S∣−i+1⇔−S∣S∣−i+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∣−(i−1)+1)<sum((i−1)+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))
[Li−1,Ri−1)=[sum(i−1),suf(∣S∣−(i−1)+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=1∑s−1(Ri−Li)=i=1∑s−1suf(∣S∣−i+1)−i=1∑s−1sum(i)=i=1∑s−1(s−i)S∣S∣−i+1−i=1∑s−1(s−i)Si
综上所述,我们得用数据结构维护一个单增序列每段子串的
∑
S
i
\sum S_i
∑Si,
∑
S
i
⋅
i
\sum S_i\cdot i
∑Si⋅i ,以及同时,该数据结构还得支持
- 从某个值后面添加一个数
- 删除某个值的数
那么最适合的数据结构就是平衡树了!虽然有点卡常。
官解做法:
推理结论都差不多,只不过角度不同:官解是用全集 - 空隙,然后证明了最左和最右两端的空隙都是连续的(定义了
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(n−k+1),然后直接告诉你它在左半边是单减的(Let’s note two simply things: …))。
然后也是二分边界,维护元素和以及元素带权和,只不过用的是离散化+线段树,常数小一些,但是自我感觉想起来很麻烦。
CODE
无旋Treap
,中间求sum
和suf
时卡了卡常。
无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的代码
[CF1500E] Subset Trick (平衡树)的更多相关文章
- Codeforces 420D Cup Trick 平衡树
Cup Trick 平衡树维护一下位置. #include<bits/stdc++.h> #include <bits/extc++.h> #define LL long lo ...
- Codeforces 1500E - Subset Trick(线段树)
Codeforces 题目传送门 & 洛谷题目传送门 一道线段树的套路题(似乎 ycx 会做这道题?orzorz!!11) 首先考虑什么样的 \(x\) 是"不合适"的,我 ...
- codeforces 420D Cup Trick
codeforces 420D Cup Trick 题意 题解 官方做法需要用到线段树+平衡树(? 如果数据小的话似乎可以用莫队).然后代码好长好长.我补了一个只要用到树状数组的做法. 代码 #inc ...
- SGU - 507 启发式合并维护平衡树信息
题意:给定一颗树,每个叶子节点\(u\)都有权值\(val[u]\),求每个非叶子节点子树的最小叶子距离,若该子树只有一个叶子节点,输出INF 貌似本来是一道树分治(并不会)的题目,然而可以利用平衡树 ...
- 平衡树(Splay、fhq Treap)
Splay Splay(伸展树)是一种二叉搜索树. 其复杂度为均摊\(O(n\log n)\),所以并不可以可持久化. Splay的核心操作有两个:rotate和splay. pushup: 上传信息 ...
- 平衡树 & LCT
1. 非旋 Treap(FHQ Treap) 1.1. 算法简介 FHQ Treap 的功能非常强大.它涵盖了 Treap 几乎所有的功能 所以我非常后悔学了 Treap,浪费时间. FHQ 的核心思 ...
- Pytorch技法:继承Subset类完成自定义数据拆分
我们在<torch.utils.data.DataLoader与迭代器转换>中介绍了如何使用Pytorch内置的数据集进行论文实现,如torchvision.datasets.下面是加载内 ...
- EEG preprocessing - A Trick Before Doing ICA
EEGLab maillist My ICs don't have high power in low frequency is b/c I do a small trick here. before ...
- [LeetCode] Partition Equal Subset Sum 相同子集和分割
Given a non-empty array containing only positive integers, find if the array can be partitioned into ...
随机推荐
- Java常用类-包装类
包装类 Java中的基本类型功能简单,不具备对象的特性,为了使基本类型具备对象的特性,所以出现了包装类,就可以像操作对象一样操作基本类型数据;包装类不是为了取代基本数据类型,而是在数据类型需要使用 ...
- 第六章、PXE高效网络装机、Kickstart无人值守安装
目录 一.部署PXE远程安装服务 1PXE定义 2PXE服务优点 3搭建网络体系前提条件 4PXE实现过程讲解 二.搭建PXE远程安装服务器 三.Kickstart无人值守安装 一.部署PXE远程安装 ...
- BUUCTF-ningen
ningen 从16进制看可以发现其中有压缩包,存在着504b0304,使用binwalk分离即可 压缩包带密码,根据提示是四位纯数字 使用ARCHPR破解即可
- 解开XAML的邪恶面纱
什么是XAML,首先我们看下它的外观 <Window x:Class="Blend_WPF.WindowStyle" xmlns="http://sc ...
- 使用vue实现排序算法演示动画
缘起 最近做的一个小需求涉及到排序,界面如下所示: 因为项目是使用vue的,所以实现方式很简单,视图部分不用管,本质上就是操作数组,代码如下: { // 上移 moveUp (i) { // 把位置i ...
- CompletableFuture的入门
runAsync 和 supplyAsync runAsync接受一个Runable的实现,无返回值 CompletableFuture.runAsync(()->System.out.prin ...
- CF141E Clearing Up 题解
思路分析 自认为是一道很好的思维题. 直接看上去的想法是: 跑一个生成树,每一次加的边颜色交替进行,直到拉出生成树. 仔细想想,发现可能无法保证最后是一棵树而不是森林,也是说输出都是 \(-1\) . ...
- java.Scanner 拓展用法
package study5ran2yl.study; import java.util.Scanner; public class demo11 { public static void main( ...
- ooday07 Java_接口
笔记: 接口: 是一种引用数据类型 由interface定义 只能包含常量和抽象方法------默认权限是public 接口不能被实例化 接口是需要被实现/继承,实现/派生类:必须重写所有抽象方法 一 ...
- V.Internet基础及应用