[题解] CF85D Sum of Medians
前言
码完之后去看题解,代码都好短……
终于没有用快读啦~
题意
对于一个初始为空的集合,有三种操作:
- add:向集合里加入数 \(x\) ,保证加入前集合中没有数 \(x\) ;
- del:从集合中删除数 \(x\) ,保证删除前集合中有 \(x\) ;
- sum:询问将集合里的数从小到大排序后,求下标 \(i\) 满足 \(5\) 余 \(3\) 的数的和。
现有 \(n\) 次操作,对于每个查询操作,输出答案。
思路
看到插入、删除、区间操作而且不考虑序列的顺序,首先就想到了平衡树(splay)。
先来说 splay 中节点包含的信息:
- \(wei[5]\) :当前节点排序后的下标对于 \(5\) 取模后为 \(wei\) 的下标,并存入该点的权值。简单来说, \(wei[j]\) 就是当前节点对于下标 \(i\) 满足 \(5\) 余 \(j\) 的数的和的单点贡献。
- \(sum[5]\) :当前节点排序后,子树中的 \(wei\) 的和。
- 其他的就是平常的 splay 了,能打懒标记就行。
在向上 update 重塑节点信息的时候 :
\(sum_{pos}=sum_{ls} + sum_{rs} + wei_{pos}\)
首先对于 \(1\) 操作,二话不说首先 insert 。
应当考虑当前节点的贡献 \(wei\) 。贡献的大小 \(x\) 已知,只需要弄清楚权值对 \(1\) ~ \(5\) 哪一个 \(wei\) 做贡献的问题了。只需要查询 \(x\) 前驱的排名加 \(1\) 即可。
再是考虑插入 \(x\) 对于其他点的影响。可以发现,插入 \(x\) 对于大于 \(x\) 的数有影响,会使得这些点的 \(sum[i]\) 和 \(wei[i]\) 变为 \(sum[(i+1)\%5]\) 和 \(wei[(i+1)\%5]\) 。将权值为 \(x\) 的点 splay 到根节点,大于 \(x\) 的数全都在其右子树中了。然后只用修改右儿子的 \(wei\) 和 \(sum\) ,对于右儿子美美地打上懒标记就行了。
然后是删除操作,现将他 splay 到根节点,把他的所有有关权值的信息清空了,避免对于之后的删除操作造成毁灭性的影响。
可以发现,删除 \(x\) 对于大于 \(x\) 的数有影响,会使得这些点的 \(sum[i]\) 和 \(wei[i]\) 变为 \(sum[(i-1)\%5]\) 和 \(wei[(i-1)\%5]\) 。接下来的操作就类似于操作1里面的操作了。
最后,就是最简单的查询操作了,直接访问根节点的信息就行了。
Code
时间复杂度为 \(O(5n\log n)\) ,最慢的点 \(1372ms\) 。
#include <cstdio>
#define INF 1e15
#define int long long
const int MAXN = 1e5 + 5;
struct Splay_Node {
int fa, cnt, siz, val, tag, son[2], wei[5], sum[5];
#define ls t[pos].son[0]
#define rs t[pos].son[1]
};
struct Splay_Tree {
Splay_Node t[MAXN];
int root, tot, Top, stk[MAXN];
bool Ident(int pos) {
return t[t[pos].fa].son[1] == pos;
}
void New(int val, int fa) {
t[++tot].fa = fa;
t[tot].val = val;
t[tot].cnt = t[tot].siz = 1;
}
void Build() {
New(-INF, 0);
root = tot;
New(INF, root);
t[root].son[1] = tot;
}
void Update(int pos) {
for(int i = 0; i < 5; i++) t[pos].sum[i] = t[ls].sum[i] + t[rs].sum[i] + t[pos].wei[i];
t[pos].siz = t[ls].siz + t[rs].siz + t[pos].cnt;
}
void Connect(int pos, int fa, int how) {
t[pos].fa = fa;
t[fa].son[how] = pos;
}
void Push_Down(int pos) {
if(!t[pos].tag) return;
int tmp[5];
if(ls) {
for(int i = 0; i < 5; i++) tmp[(i + t[pos].tag + 5) % 5] = t[ls].sum[i];
for(int i = 0; i < 5; i++) t[ls].sum[i] = tmp[i];
for(int i = 0; i < 5; i++) tmp[(i + t[pos].tag + 5) % 5] = t[ls].wei[i];
for(int i = 0; i < 5; i++) t[ls].wei[i] = tmp[i];
t[ls].tag = (t[ls].tag + t[pos].tag + 5) % 5;
}
if(rs) {
for(int i = 0; i < 5; i++) tmp[(i + t[pos].tag + 5) % 5] = t[rs].sum[i];
for(int i = 0; i < 5; i++) t[rs].sum[i] = tmp[i];
for(int i = 0; i < 5; i++) tmp[(i + t[pos].tag + 5) % 5] = t[rs].wei[i];
for(int i = 0; i < 5; i++) t[rs].wei[i] = tmp[i];
t[rs].tag = (t[rs].tag + t[pos].tag + 5) % 5;
}
t[pos].tag = 0;
}
void Rotate(int pos) {
int fa = t[pos].fa, grand = t[fa].fa;
int how1 = Ident(pos), how2 = Ident(fa);
Connect(pos, grand, how2);
Connect(t[pos].son[how1 ^ 1], fa, how1);
Connect(fa, pos, how1 ^ 1);
Update(fa);
Update(pos);
}
void Splay(int pos, int to) {
int tmp = pos;
Top = 0;
stk[++Top] = tmp;
while(tmp) stk[++Top] = tmp = t[tmp].fa;
while(Top) Push_Down(stk[Top--]);
for(int fa = t[pos].fa; t[pos].fa != to; Rotate(pos), fa = t[pos].fa)
if(t[fa].fa != to) Ident(pos) == Ident(fa) ? Rotate(fa) : Rotate(pos);
if(!to) root = pos;
}
int Find(int pos, int val) {
if(!pos) return 0;
Push_Down(pos);
if(val == t[pos].val) return pos;
else if(val < t[pos].val) return Find(ls, val);
return Find(rs, val);
}
void Insert(int &pos, int val, int fa) {
if(!pos) {
New(val, fa);
Splay(pos = tot, 0);
return;
}
Push_Down(pos);
if(val == t[pos].val) {
t[pos].cnt++;
Splay(pos, 0);
}
else if(val < t[pos].val) Insert(ls, val, pos);
else Insert(rs, val, pos);
}
void Erase(int val) {
int pos = Find(root, val);
if(!pos) return;
if(t[pos].cnt > 1) {
t[pos].cnt--;
Splay(pos, 0);
return;
}
Splay(pos, 0);
int l = ls, r = rs;
while(t[l].son[1]) l = t[l].son[1];
while(t[r].son[0]) r = t[r].son[0];
Splay(l, 0);
Splay(r, l);
t[r].son[0] = 0;
}
int Query_Rank(int pos, int val) {
if(!pos) return 0;
if(val == t[pos].val) {
int res = t[ls].siz + 1;
Splay(pos, 0);
return res;
}
else if(val < t[pos].val) return Query_Rank(ls, val);
int res = t[ls].siz + t[pos].cnt;
return Query_Rank(rs, val) + res;
}
int Query_Pre(int val) {
int pos, res, newroot;
pos = newroot = root;
while(pos) {
Push_Down(pos);
if(t[pos].val < val) {
res = t[pos].val;
pos = rs;
}
else pos = ls;
}
Splay(newroot, 0);
return res;
}
};
Splay_Tree tree;
int n;
signed main() {
tree.Build();
scanf("%lld", &n);
char opt[3];
for(int i = 1, x; i <= n; i++) {
scanf("%s", opt);
if(opt[0] == 'a') {
scanf("%lld", &x);
tree.Insert(tree.root, x, 0);
int pre = tree.Query_Pre(x);
int pos = tree.Find(tree.root, x);
int prerk = tree.Query_Rank(tree.root, pre);
prerk %= 5;
tree.Splay(pos, 0);
tree.t[pos].wei[prerk] = tree.t[pos].sum[prerk] = x;
int tmp[5];
if(tree.rs) {
for(int i = 0; i < 5; i++) tmp[(i + 1) % 5] = tree.t[tree.rs].sum[i];
for(int i = 0; i < 5; i++) tree.t[tree.rs].sum[i] = tmp[i];
for(int i = 0; i < 5; i++) tmp[(i + 1) % 5] = tree.t[tree.rs].wei[i];
for(int i = 0; i < 5; i++) tree.t[tree.rs].wei[i] = tmp[i];
tree.t[tree.rs].tag += tree.t[pos].tag;
}
tree.t[tree.rs].tag++;
tree.Update(pos);
}
else if(opt[0] == 'd') {
scanf("%lld", &x);
int pos = tree.Find(tree.root, x);
tree.Splay(pos, 0);
int tmp[5];
for(int i = 0; i < 5; i++) tree.t[pos].wei[i] = tree.t[pos].sum[i] = 0;
if(tree.rs) {
for(int i = 0; i < 5; i++) tmp[(i + 4) % 5] = tree.t[tree.rs].sum[i];
for(int i = 0; i < 5; i++) tree.t[tree.rs].sum[i] = tmp[i];
for(int i = 0; i < 5; i++) tmp[(i + 4) % 5] = tree.t[tree.rs].wei[i];
for(int i = 0; i < 5; i++) tree.t[tree.rs].wei[i] = tmp[i];
tree.t[tree.rs].tag += tree.t[pos].tag;
}
tree.t[tree.rs].tag--;
tree.Erase(x);
}
else printf("%lld\n", tree.t[tree.root].sum[3]);
}
return 0;
}