[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 ...
随机推荐
- AtCoder ABC 242 题解
AtCoder ABC 242 题解 A T-shirt 排名前 \(A\) 可得 T-shirt 排名 \([A+1,B]\) 中随机选 \(C\) 个得 T-shirt 给出排名 \(X\) ,求 ...
- DYOJ 【20220317模拟赛】瞬间移动 题解
瞬间移动 题意 三维空间中从 \((0,0,0)\) 开始,每次移动 1,问刚好走 \(N\) 次能到 \((X,Y,Z)\) 的方案数 \(N\le10^7\),答案模 \(998244353\) ...
- c++ 乘法逆元
主要参考:OI-WIKI 为什么要逆元 当一个题目让你求方案数时常要取余,虽然 \((a+b)\% p=(a\% p+b\% p)\%p\) \((a-b)\% p=(a\% p-b\% p)\%p\ ...
- vscode远程调试c++
0.背景 最近在学习linux webserver开发,需要在linux下调试自己的C/C++代码,但是linux下不像在windows下,直接Visio Studio或者其它集成开发环境那么方便,现 ...
- 菜鸟学git的基本命令及常见错误
Git init //在当前项目工程下履行这个号令相当于把当前项目git化,变身!\ git config --global user.name "xxx" # 配置用户名 git ...
- 开发工具-Typora编辑器下载地址
更新记录 2022年6月10日 完善标题. 比较好用的Markdown编辑器了,哈哈. https://typoraio.cn/
- .NET中按预定顺序执行任务
更新记录 本文迁移自Panda666原博客,原发布时间:2021年7月1日. 一.说明 在.NET中线程可以定义按先后顺序进行执行,适合部分有先后次序的业务逻辑.Task也可以按照预定义的先后顺序执行 ...
- 如何利用 RPA 实现自动化获客?
大家好,我是二哥.前高级技术专家 & 增长黑客,现一枚爱折腾的小小创业者,专注于 RPA & SaaS 软件这块.这次给大家带来如何利用 RPA 实现自动化获客 一.RPA 是什么?难 ...
- Obsidian基础教程
Obsidian基础教程 相关链接 2021年新教程 - Obsidian中文教程 - Obsidian Publish 软通达 基础设置篇 1. 开启实时预览 开启实时预览模式,所见即所得 打开设置 ...
- linux web漏洞扫描arachni
1. 下载arachni https://www.arachni-scanner.com/download/下载Linux x86 64bit 2. 上次解压直接使用 tar xzf arachni- ...