CF85D Sum of Medians

前言

題目鏈接:洛谷

題目鏈接:CodeForces

碼完之後去看題解,程式碼都好短……

終於沒有用快讀啦~

題意

對於一個初始為空的集合,有三種操作:

  1. add:向集合里加入數 \(x\) ,保證加入前集合中沒有數 \(x\) ;
  2. del:從集合中刪除數 \(x\) ,保證刪除前集合中有 \(x\) ;
  3. 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;
}