1. 程式人生 > 實用技巧 >洛谷P4848 嶗山白花蛇草水 權值線段樹+KDtree

洛谷P4848 嶗山白花蛇草水 權值線段樹+KDtree

題目描述

神犇 \(Aleph\)\(SDOI\ Round2\) 前立了一個 \(flag\):如果進了省隊,就現場直播喝嶗山白花蛇草水。憑藉著神犇 \(Aleph\) 的實力,他輕鬆地進了山東省省隊,現在便是他履行諾言的時候了。蒟蒻 \(Bob\) 特地為他準備了 \(999,999,999,999,999,999\) 瓶嶗山白花蛇草水,想要灌神犇 \(Aleph\)。神犇 \(Aleph\) 求(跪著的)蒟蒻 \(Bob\) 不要灌他,由於神犇 \(Aleph\) 是神犇,蒟蒻 \(Bob\) 最終答應了他的請求,但蒟蒻 \(Bob\) 決定將計就計,也讓神犇 $Aleph4 回答一些問題。

具體說來,蒟蒻 \(Bob\) 會在一個寬敞的廣場上放置一些嶗山白花蛇草水(可視為二維平面上的一些整點),然後詢問神犇 \(Aleph\) 在矩形區域 \(x_1\le x\le x_2,y_1\le y\le y_2\) 中,嶗山白花蛇草水瓶數第 \(k\) 多的是多少。為了避免麻煩,蒟蒻 \(Bob\) 不會在同一個位置放置兩次或兩次以上的嶗山白花蛇草水,但蒟蒻 \(Bob\) 想為難一下神犇 \(Aleph\),希望他能在每次詢問時立刻回 答出答案。

神犇 \(Aleph\) 不屑於做這種問題,所以把這個問題交給了你。

輸入格式

輸入的第一行為兩個正整數 \(n\)\(q\),表示橫縱座標的範圍和蒟蒻 \(Bob\)

的操作次數(包括放置次數和詢問次數)。

接下來 \(q\) 行,每行代表蒟蒻 \(Bob\) 的一個操作,操作格式如下:
首先第一個數字 \(\mathrm{type}\),表示操作種類。\(\mathrm{type}=1\) 表示放置,\(\mathrm{type}=2\) 表示詢問。
\(\mathrm{type}=1\),接下來會有三個正整數 \(x, y, v\),表示在座標整點 \((x, y)\) 放置v瓶嶗山白花蛇草水。
\(\mathrm{type}=2\),接下來會有五個正整數 \(x_1, y_1, x_2, y_2, k\),表示詢問矩形區域 \(x_1\le x\le x_2,y_1\le y\le y_2\)

​ 中,嶗山白花蛇草水瓶數第 \(k\) 多的是多少。

為了體現程式的線上性,你需要將每次讀入的資料(除了 \(\mathrm{type}\) 值)都異或 \(\mathrm{lastans}\),其中 \(\mathrm{lastans}\) 表示上次詢問的答 案。如果上次詢問的答案為 \(NAIVE!ORZzyz\).(見樣例輸出),則將 \(\mathrm{lastans}\) 置為 \(0\)。初始時的 \(\mathrm{lastans}\)\(0\)。 初始時平面上不存在嶗山白花蛇草水。

輸出格式

對於每個 \(\mathrm{type}=2\) 的操作,一行輸出嶗山白花蛇草水瓶數第 \(k\) 多的是多少。若不存在第 \(k\) 多的瓶數, 請輸出 \(NAIVE!ORZzyz.\)

輸入輸出樣例

輸入 #1

10 7
1 1 1 1
1 2 2 3
1 4 1 2
1 3 4 4
2 1 1 4 1 3
2 2 2 3 5 4
2 2 1 4 4 2

輸出 #1

NAIVE!ORZzyz.
NAIVE!ORZzyz.
3

說明/提示

對於所有資料,\(n\le500000\)\(q\le100000\)\(1\le x, y\le n\)\(1\le v\le 10^9\)\(1\le x_1\le x_2\le n\)\(1\le y_1\le y_2\le n\)\(1\le k\le q\)

分析

在給定的矩形內查詢第 \(k\) 大的權值

動態加點,強制線上

這種題一個非常好想的做法就是開一個小根堆維護最大的 \(k\) 個元素

每次只更新隊首的元素

這種做法在 \(k\) 比較小的情況下是沒有問題的

然而這道題 \(k\) 的範圍達到了 \(10^5\) ,顯然會 \(T\) 到飛起

所以我們需要另一種能夠支援查詢區間 \(k\) 大值的資料結構,比如權值線段樹

權值線段樹的每個節點上都維護一棵 \(kdtree\)

\(kdtree\) 中儲存權值為當前節點下標的所有點的座標

因為要動態加點,為了防止二叉樹退化成一條鏈,需要借鑑替罪羊的思想暴力重構

要注意的是,暴力重構時,不能這樣寫

void dfs(rg int da){
	sta[++tp]=da;
	jl[tp]=ktr[da];
	if(ktr[da].lc) dfs(ktr[da].lc);
	if(ktr[da].rc) dfs(ktr[da].rc);
}

而要這樣寫

void dfs(rg int da){
	sta[++tp]=da;
	jl[tp].d[0]=ktr[da].d[0];
	jl[tp].d[1]=ktr[da].d[1];
	if(ktr[da].lc) dfs(ktr[da].lc);
	if(ktr[da].rc) dfs(ktr[da].rc);
}

因為你直接繼承 \(min\)\(max\) 會對之後節點的更新造成影響

程式碼

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<cmath>
#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,INF=1e9;
const double alpha=0.75;
inline int Max(rg int aa,rg int bb){
	return aa>bb?aa:bb;
}
inline int Min(rg int aa,rg int bb){
	return aa<bb?aa:bb;
}
int orz,kcnt,sta[maxn],tp;
struct kdt{
	int lc,rc,mn[2],mx[2],d[2],siz;
	friend bool operator <(const kdt& A,const kdt& B){
		return A.d[orz]<B.d[orz];
	}
}ktr[maxn*5],jl[maxn*5];
inline int newnode(){
	if(tp) return sta[tp--];
	else return ++kcnt;
}
void push_up(rg int da){
	rg int lc=ktr[da].lc,rc=ktr[da].rc;
	for(rg int i=0;i<2;i++){
		ktr[da].mn[i]=ktr[da].mx[i]=ktr[da].d[i];
		if(lc){
			ktr[da].mn[i]=Min(ktr[da].mn[i],ktr[lc].mn[i]);
			ktr[da].mx[i]=Max(ktr[da].mx[i],ktr[lc].mx[i]);
		}
		if(rc){
			ktr[da].mn[i]=Min(ktr[da].mn[i],ktr[rc].mn[i]);
			ktr[da].mx[i]=Max(ktr[da].mx[i],ktr[rc].mx[i]);
		}
	}
	ktr[da].siz=ktr[lc].siz+ktr[rc].siz+1;
}
int build(rg int l,rg int r,rg int pl){
	orz=pl;
	rg int da=newnode(),mids=(l+r)>>1;
	std::nth_element(jl+l,jl+mids,jl+r+1);
	ktr[da]=jl[mids];
	if(l<mids) ktr[da].lc=build(l,mids-1,!pl);
	if(mids<r) ktr[da].rc=build(mids+1,r,!pl);
	push_up(da);
	return da;
}
void dfs(rg int da){
	sta[++tp]=da;
	jl[tp].d[0]=ktr[da].d[0];
	jl[tp].d[1]=ktr[da].d[1];
	if(ktr[da].lc) dfs(ktr[da].lc);
	if(ktr[da].rc) dfs(ktr[da].rc);
}
void check(rg int &da,rg int pl){
	rg int lc=ktr[da].lc,rc=ktr[da].rc;
	if(ktr[lc].siz>ktr[da].siz*alpha || ktr[rc].siz>ktr[da].siz*alpha){
		tp=0;
		dfs(da);
		da=build(1,tp,pl);
	}
}
int ad(rg int da,rg int nx,rg int ny,rg int pl){
	if(!da){
		da=newnode();
		ktr[da].mn[0]=ktr[da].mx[0]=ktr[da].d[0]=nx;
		ktr[da].mn[1]=ktr[da].mx[1]=ktr[da].d[1]=ny;
		ktr[da].siz=1;
		return da;
	}
	if(pl==0){
		if(nx<ktr[da].d[0]) ktr[da].lc=ad(ktr[da].lc,nx,ny,!pl);
		else ktr[da].rc=ad(ktr[da].rc,nx,ny,!pl);
	} else {
		if(ny<ktr[da].d[1]) ktr[da].lc=ad(ktr[da].lc,nx,ny,!pl);
		else ktr[da].rc=ad(ktr[da].rc,nx,ny,!pl);
	}
	push_up(da);
	if(ktr[da].siz>6) check(da,pl);
	return da;
}
int cx(rg int da,rg int a1,rg int a2,rg int b1,rg int b2){
	if(!da) return 0;
	if(ktr[da].mx[0]<a1 || ktr[da].mn[0]>a2 || ktr[da].mx[1]<b1 || ktr[da].mn[1]>b2) return 0;
	if(ktr[da].mx[0]<=a2 && ktr[da].mn[0]>=a1 && ktr[da].mx[1]<=b2 && ktr[da].mn[1]>=b1){
		return ktr[da].siz;
	}
	rg int nans=0;
	if(ktr[da].d[0]<=a2 && ktr[da].d[0]>=a1 && ktr[da].d[1]<=b2 && ktr[da].d[1]>=b1)  nans++;
	nans+=cx(ktr[da].lc,a1,a2,b1,b2);
	nans+=cx(ktr[da].rc,a1,a2,b1,b2);
	return nans;
}
struct trr{
	int lc,rc;
}tr[maxn*10];
int rk[maxn],cnt,rt;
int xg(rg int da,rg int l,rg int r,rg int wz,rg int nx,rg int ny){
	if(!da) da=++cnt;
	rg int mids=(l+r)>>1;
	rk[da]=ad(rk[da],nx,ny,0);
	if(l==r) return da;
	if(wz<=mids) tr[da].lc=xg(tr[da].lc,l,mids,wz,nx,ny);
	else tr[da].rc=xg(tr[da].rc,mids+1,r,wz,nx,ny);
	return da;
}
int trcx(rg int da,rg int l,rg int r,rg int kth,rg int a1,rg int a2,rg int b1,rg int b2){
	if(l==r) return l;
	rg int nans=cx(rk[tr[da].rc],a1,a2,b1,b2),mids=(l+r)>>1;
	if(nans>=kth) return trcx(tr[da].rc,mids+1,r,kth,a1,a2,b1,b2);
	else return trcx(tr[da].lc,l,mids,kth-nans,a1,a2,b1,b2);
}
int n,q,latans;
int main(){
	n=read(),q=read();
	rg int aa,bb,cc,dd,ee,ff;
	for(rg int i=1;i<=q;i++){
		aa=read(),bb=read(),cc=read(),dd=read();
		bb^=latans,cc^=latans,dd^=latans;
		if(aa==1){
			rt=xg(rt,1,INF,dd,bb,cc);
		} else {
			ee=read(),ff=read();
			ee^=latans,ff^=latans;
			if(cx(rk[rt],bb,dd,cc,ee)<ff){
				printf("NAIVE!ORZzyz.\n");
				latans=0;
			} else {
				printf("%d\n",latans=trcx(rt,1,INF,ff,bb,dd,cc,ee));
			}
		}
	}
	return 0;
}