洛谷P3285 [SCOI2014]方伯伯的OJ 動態開點平衡樹

liuchanglc發表於2021-03-04

洛谷P3285 [SCOI2014]方伯伯的OJ 動態開點平衡樹

題目描述

方伯伯正在做他的 \(Oj\) 。現在他在處理 \(Oj\) 上的使用者排名問題。 \(Oj\) 上註冊了 \(n\) 個使用者,編號為 \(1 \sim n\),一開始他們按照編號排名。

方伯伯會按照心情對這些使用者做以下四種操作,修改使用者的排名和編號:

\(1\).操作格式為 \(1\ x\ y\),意味著將編號為$ x$ 的使用者編號改為 \(y\) ,而排名不變,執行完該操作後需要輸出該使用者在佇列中的位置,資料保證 \(x\) 必然出現在佇列中,同時,\(y\) 是一個當前不在排名中的編號。

\(2\).操作格式為\(2\ x\),意味著將編號為 \(x\) 的使用者的排名提升到第一位,執行完該操作後需要輸出執行該操作前編號為 \(x\) 使用者的排名。

\(3\).操作格式為 \(3\ x\) ,意味著將編號為 \(x\) 的使用者的排名降到最後一位,執行完該操作後需要輸出執行該操作前編號為 \(x\) 使用者的排名。

4.操作格式為 \(4\ k\),意味著查詢當前排名為 \(k\) 的使用者編號,執行完該操作後需要輸出當前操作使用者的編號。

但同時為了防止別人監聽自己的工作,方伯伯對他的操作進行了加密,即將四種操作的格式分別改為了:

  • \(1\ x+a\ y+a\)
  • \(2\ x+a\)
  • \(3\ x+a\)
  • \(4\ k+a\)

其中 \(a\) 為上一次操作得到的輸出,一開始 \(a=0\)

例如:上一次操作得到的輸出是 \(5\) 這一次操作的輸入為:\(1\ 13\ 15\)

因為這個輸入是經過加密後的,所以你應該處理的操作是 \(1\ 8\ 10\)

現在你截獲了方伯伯的所有操作,希望你能給出結果。

輸入輸出格式

輸入格式

輸入的第 \(1\) 行包含 \(2\) 個用空格分隔的整數 \(n\)\(m\) ,表示初始使用者數和運算元。此後有 \(m\) 行,每行是一個詢問,詢問格式如上所示。

輸出格式

輸出包含 \(m\) 行。每行包含一個整數,其中第 \(i\) 行的整數表示第 \(i\) 個操作的輸出。

輸入輸出樣例

輸入樣例 #1

10 10
1 2 11
3 13
2 5
3 7
2 8
2 10
2 11
3 14
2 18
4 9

輸出樣例 #1

2
2
2
4
3
5
5
7
8
11

說明

對於 \(100\%\) 的資料,\(1 \leq n \leq 10^8\)\(1 \leq m \leq 10^5\)

輸入保證對於所有的操作 \(1\)\(2\)\(3\)\(x\) 必然已經出現在佇列中,同時對於所有操作 \(1\)\(1 <= y <= 2 \times 10^8\),並且 \(y\) 沒有出現在佇列中。

對於所有操作 \(4\),保證 \(1 \leq k \leq n\)

分析

無旋 \(treap\) 按照 \(siz\) 分裂可以維護序列上的資訊

暴力的做法是把 \(1 \sim n\) 中的每一個數都扔到平衡樹中

對於每一個數,用一個陣列記錄一下它在平衡樹上對應的是哪一個節點

對於操作 \(1\),只需要把 \(x\) 在平衡樹上所代表的節點分裂出來,然後把當前節點的權值改為 \(y\),再合併回去即可

對於操作 \(2\),通過一個函式 \(getrk(x)\) 查詢平衡樹上編號為 \(x\) 的節點在序列中的位置

具體的實現就是一直跳父親,如果當前節點是父親節點的右兒子,那麼將左兒子和父親節點的 \(siz\) 累加

父親的資訊在 \(pushup\) 的時候更新,而且在進入 \(merge\)\(split\) 的時候要把當前節點的父親節點置為 \(0\)

找到排名之後把當前的節點和排名在它前面的節點都 \(split\) 出來,交換一下位置再合併回去即可

操作 \(3\) 同理

對於操作 \(4\),從根節點開始在平衡樹上二分查詢即可

但是 \(n\) 的範圍達到了 \(1e8\),這樣做複雜度肯定不對

發現 \(m\) 的範圍比較小

也就是說有一些位置是不會被操作的,而且這樣的位置很多

所以我們可以改變平衡樹中維護的資訊

將維護一個節點改為維護一個區間,可以理解為動態開點

節點上要多維護區間的左右端點以及長度

當對於某一個點進行操作時,就把當前節點所在的區間分成幾個小區間扔進平衡樹中

同時要開一個 \(map\) 記錄一下每一個節點掌管的是哪一個區間

方便對於一個數快速查詢它被哪一個節點所掌管

具體實現的過程中還是有很多細節的

程式碼

#include<cstdio>
#include<algorithm>
#include<map>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<ctime>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5;
int cnt,rt,rt1,rt2,rt3,rt4,n,m;
std::map<int,int> mp;
#define mit std::map<int,int>::iterator
struct trr{
	int ch[2],siz,rd,fa,l,r,len;
}tr[maxn];
void push_up(rg int da){
	tr[da].siz=tr[tr[da].ch[0]].siz+tr[tr[da].ch[1]].siz+tr[da].len;
	tr[tr[da].ch[0]].fa=da,tr[tr[da].ch[1]].fa=da;
}
int ad(rg int l,rg int r){
	tr[++cnt].rd=rand();
	mp[r]=cnt;
	tr[cnt].l=l;
	tr[cnt].r=r;
	tr[cnt].len=r-l+1;
	tr[cnt].siz=tr[cnt].len;
	return cnt;
}
void split(rg int now,rg int val,rg int& x,rg int& y){
	tr[now].fa=0;
	if(!now){
		x=y=0;
		return;
	}
	if(tr[tr[now].ch[0]].siz+tr[now].len<=val){
		x=now;
		split(tr[now].ch[1],val-tr[tr[now].ch[0]].siz-tr[now].len,tr[now].ch[1],y);
	} else {
		y=now;
		split(tr[now].ch[0],val,x,tr[now].ch[0]);
	}
	push_up(now);
}
int bing(rg int aa,rg int bb){
	tr[aa].fa=0,tr[bb].fa=0;
	if(!aa || !bb) return aa+bb;
	if(tr[aa].rd<tr[bb].rd){
		tr[aa].ch[1]=bing(tr[aa].ch[1],bb);
		push_up(aa);
		return aa;
	} else {
		tr[bb].ch[0]=bing(aa,tr[bb].ch[0]);
		push_up(bb);
		return bb;
	}
}
int getrk(rg int da){
	rg int nans=tr[tr[da].ch[0]].siz+tr[da].len;
	while(tr[da].fa){
		if(tr[tr[da].fa].ch[1]==da){
			da=tr[da].fa;
			nans+=tr[tr[da].ch[0]].siz+tr[da].len;
		} else {
			da=tr[da].fa;
		}
	}
	return nans;
}
int kth(rg int da,rg int k){
	while(1){
		if(k>tr[tr[da].ch[0]].siz+tr[da].len){
			k-=tr[tr[da].ch[0]].siz+tr[da].len;
			da=tr[da].ch[1];
		} else if(k<=tr[tr[da].ch[0]].siz+tr[da].len && k>=tr[tr[da].ch[0]].siz+1){
			return da;
		} else {
			da=tr[da].ch[0];
		}
	}
}
int latans;
int main(){
	srand(time(0));
	n=read(),m=read();
	rt=ad(1,n);
	rg int aa,bb,cc,dd,ee;
	rg mit it;
	for(rg int i=1;i<=m;i++){
		aa=read(),bb=read();
		bb-=latans;
		if(aa==1){
			cc=read();
			cc-=latans;
			it=mp.lower_bound(bb);
			dd=it->second;
			ee=getrk(dd);
			mp.erase(it);
			split(rt,ee-1,rt,rt1);
			split(rt1,tr[dd].len,rt1,rt2);
			if(tr[dd].l!=bb) rt=bing(rt,ad(tr[dd].l,bb-1));
			rt=bing(rt,ad(cc,cc));
			if(tr[dd].r!=bb) rt=bing(rt,ad(bb+1,tr[dd].r));
			rt=bing(rt,rt2);
			printf("%d\n",latans=getrk(mp[cc]));
		} else if(aa==2){
			it=mp.lower_bound(bb);
			dd=it->second;
			ee=getrk(dd);
			mp.erase(it);
			printf("%d\n",latans=ee-(tr[dd].r-bb));
			split(rt,ee-1,rt,rt1);
			split(rt1,tr[dd].len,rt1,rt2);
			rt=bing(ad(bb,bb),rt);
			if(tr[dd].l!=bb) rt=bing(rt,ad(tr[dd].l,bb-1));
			if(tr[dd].r!=bb) rt=bing(rt,ad(bb+1,tr[dd].r));
			rt=bing(rt,rt2);
		} else if(aa==3){
			it=mp.lower_bound(bb);
			dd=it->second;
			ee=getrk(dd);
			mp.erase(it);
			printf("%d\n",latans=ee-(tr[dd].r-bb));
			split(rt,ee-1,rt,rt1);
			split(rt1,tr[dd].len,rt1,rt2);
			if(tr[dd].l!=bb) rt=bing(rt,ad(tr[dd].l,bb-1));
			if(tr[dd].r!=bb) rt=bing(rt,ad(bb+1,tr[dd].r));
			rt=bing(rt,rt2);
			rt=bing(rt,ad(bb,bb));
		} else {
			dd=kth(rt,bb);
			ee=getrk(dd);
			printf("%d\n",latans=tr[dd].r-(ee-bb));
		}
	}
	return 0;
}

相關文章