洛谷 P3285 - 方伯伯的OJ

萌新第二次写洛谷题库题题解,往期回顾然而没想到这题的题解入口被关闭了

洛谷题目页面传送门

\(n\)名用户,初始排名为\(i\)的用户编号是\(i\)。你需要支持以下\(4\)\(q\)次操作:

  1. \(\texttt1\ x\ y\):查询编号为\(x\)的用户的排名,并将编号为\(x\)的用户编号改成\(y\),保证执行之前编号\(y\)没有出现在排行榜中;
  2. \(\texttt2\ x\):查询编号为\(x\)的用户的排名,并将编号为\(x\)的用户排名提到第\(1\)名;
  3. \(\texttt3\ x\):查询编号为\(x\)的用户的排名,并将编号为\(x\)的用户排名降到第\(n\)名;
  4. \(\texttt4\ x\):查询排名为\(x\)的用户的编号。

\(n\in\left[1,10^8\right],q\in\left[1,10^5\right]\)

注意到\(n\)如此之大以至于不能将关于每名用户的信息都存下来。不妨先考虑\(n\in\left[1,10^5\right]\)的弱化版问题。

然鹅还是不会

将题目中的\(4\)种操作拆分、去重,可以得到\(3\)种修改:

  1. 将编号为\(x\)的用户编号改成\(y\)
  2. 将编号为\(x\)的用户排名提到第\(1\)名;
  3. 将编号为\(x\)的用户排名降到第\(n\)名。

\(2\)种查询:

  1. 查询编号为\(x\)的用户的排名;
  2. 查询排名为\(x\)的用户的编号。

考虑维护按排名排序后的编号们。注意到修改\(2\)和修改\(3\)改变了按排名排序后的编号序列的内部顺序,不难想到用平衡树维护,这里使用fhq-Treap。但是和普通的平衡树维护数列不同的是,普通的是要求你对某个位置或某个区间操作,而这里要求对某个值(值就是编号,互不相同,故可以这么要求)操作。于是我们还需要通过值(编号)找到位置(排名)。这该怎么维护呢?

注意到,修改\(2\)和修改\(3\)是让编号为\(x\)的用户的排名一下变成\(1/n\),然后让原本在编号为\(x\)的用户前/后面的用户们的排名\(\pm1\)。前面那个单点修改再简单不过,难点在后面那个操作。这看起来是一个区间增加,但我们是要通过编号找排名,于是要基于编号维护,不幸的是这所谓的“区间”乃是在排名上的连续段,一映射到编号上就杂乱无章,无法维护。既然无法维护,当然要想办法去掉这个所谓的“区间”增加操作啦!

不难想到,我们只关心排名与排名之间的相对大小,不考虑绝对数值。所以我们的目的只是将编号为\(x\)的用户的排名变得比TA前/后面的用户们的排名小/大。于是我们可以定义广义排名数组\(rk\)\(rk_i<rk_j\)当且仅当编号为\(i\)的用户排在编号为\(j\)的用户前面。考虑时刻记录最小广义排名\(rk_{\min}=\min\limits_{i=1}^n\{rk_i\}\)和最大广义排名\(rk_{\max}=\max\limits_{i=1}^n\{rk_i\}\),修改\(2\)只需令\(rk_x=rk_{\min}=rk_{\min}-1\),修改\(3\)类似地令\(rk_x=rk_{\max}=rk_{\max}+1\),然后平衡树内改为按广义排名排序即可。

此时我们有能力基于编号维护排名了,\(3\)种修改\(2\)种查询都变成了平衡树维护数列的基本操作,题目可做性显然

但是这只是弱问题的做法啊555……再回到\(n\in\left[1,10^8\right]\)的原问题上。

注意到每次操作最多只会改变关于\(1\)个用户的信息,总共最多只会改变关于\(q\)个用户的信息,其他那么多的用户自始至终都是naive的状态。于是我们考虑将所有极大naive区间都缩成一个点,假设这个区间为\(l,r\),那么这个大点代表了\(r-l+1\)个元素,以\(l\)为代表,他们的排名也构成区间。被修改过的用户\(x\)可以类似地用一个区间\([x,x]\)存储。至于\(rk\)数组,可以定义为一个map<int,int>,存储当前所有区间代表(即左端点)的广义排名。

接下来看看各个操作变得有什么不同。

  • 对于\(3\)种修改:在\(rk\)内二分查找找到\(x\)所在大点\([l,r]\),然后将此大点拆成至多\(3\)个新大点\([l,x-1],[x,x],[x+1,r]\)(如果非空的话),修改\([x,x]\)mrg回去。
  • 对于\(2\)种查询:注意大点\([l,r]\)代表了\(r-l+1\)个元素。直接在平衡树上往下走一遍即可。

常数是异常大的,因为每种修改都要splitmrg很多很多次。然鹅还是不需要任何卡常就AC了。

下面是AC代码:

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
mt19937 rng(20060617/*信仰优化*/);
const int QU=100000;
int n/*用户数*/,qu/*操作数*/;
map<int,int> pos;//广义排名 
int mn_pos,mx_pos;//最大、最小广义排名 
struct fhq_treap{//fhq-Treap 
	int sz/*点数*/,root/*根*/;
	struct node{unsigned key;int lson,rson,sz,real_sz/*以此节点为根的子树包含多少名用户*/,l,r/*代表的区间*/,pos/*代表值l的广义排名*/;}nd[3*QU+2];
	#define key(p) nd[p].key
	#define lson(p) nd[p].lson
	#define rson(p) nd[p].rson
	#define sz(p) nd[p].sz
	#define real_sz(p) nd[p].real_sz
	#define l(p) nd[p].l
	#define r(p) nd[p].r
	#define pos(p) nd[p].pos
	int nwnd(int l=1,int r=n,int pos=1){return nd[++sz]=node({rng(),0,0,1,r-l+1,l,r,pos}),sz;}//新建节点 
	void init(){//fhq-Treap初始化 
		nd[sz=0]=node({0,0,0,0,0,0,0,0}); 
		mn_pos=1;mx_pos=n; 
		pos[1]=1;
		root=nwnd();//初始只有[1,n]这一个区间 
	}
	void sprup(int p){sz(p)=sz(lson(p))+1+sz(rson(p));real_sz(p)=real_sz(lson(p))+r(p)-l(p)+1+real_sz(rson(p));}//上传 
	pair<int,int> split(int x,int p=-1){~p||(p=root); 
		if(!x)return mp(0,p);
		pair<int,int> sp;
		if(x<=sz(lson(p)))return sp=split(x,lson(p)),lson(p)=sp.Y,sprup(p),mp(sp.X,p);
		return sp=split(x-sz(lson(p))-1,rson(p)),rson(p)=sp.X,sprup(p),mp(p,sp.Y);
	}
	int mrg(int p,int q){
		if(!p||!q)return p|q;
		if(key(p)<key(q))return rson(p)=mrg(rson(p),q),sprup(p),p;
		return lson(q)=mrg(p,lson(q)),sprup(q),q;
	}
	int _real_pos/*在树上往下走一遍*/(int pos,int v,int p=-1){~p||(p=root);
		if(pos==pos(p))return real_sz(lson(p))+v-l(p)+1;
		if(pos<pos(p))return _real_pos(pos,v,lson(p));
		return real_sz(lson(p))+r(p)-l(p)+1+_real_pos(pos,v,rson(p));
	}
	int real_pos(int v)/*排名*/{return _real_pos((--pos.upper_bound(v))->Y,v);}
	int lss(int pos,int p=-1)/*代表值广义排名比pos小的节点数量*/{~p||(p=root);
		if(!p)return 0;
		if(pos<=pos(p))return lss(pos,lson(p));
		return sz(lson(p))+1+lss(pos,rson(p));
	}
	void chg(int v1,int v2)/*修改编号*/{
		pair<int,int> sp=split(lss((--pos.upper_bound(v1))->Y)),sp0=split(1,sp.Y);
		int mid_root=0;
		if(l(sp0.X)<=v1-1)mid_root=mrg(mid_root,nwnd(l(sp0.X),v1-1,l(sp0.X)));
		mid_root=mrg(mid_root,nwnd(v2,v2,pos[v2]=l(sp0.X)==r(sp0.X)?pos(sp0.X):v1));
		if(v1+1<=r(sp0.X))mid_root=mrg(mid_root,nwnd(v1+1,r(sp0.X),pos[v1+1]=v1+1));//分裂成至多3个区间 
		root=mrg(mrg(sp.X,mid_root),sp0.Y);//mrg回去 
	}
	void mv_top(int v)/*移顶*/{
		pair<int,int> sp=split(lss((--pos.upper_bound(v))->Y)),sp0=split(1,sp.Y);
		int mid_root=0;
		if(l(sp0.X)<=v-1)mid_root=mrg(mid_root,nwnd(l(sp0.X),v-1,l(sp0.X)));
		if(v+1<=r(sp0.X))mid_root=mrg(mid_root,nwnd(v+1,r(sp0.X),pos[v+1]=v+1));//分裂成至多3个区间
		root=mrg(mrg(mrg(nwnd(v,v,pos[v]=--mn_pos),sp.X),mid_root),sp0.Y);//mrg回去 
	} 
	void mv_btm(int v)/*移底*/{
		pair<int,int> sp=split(lss((--pos.upper_bound(v))->Y)),sp0=split(1,sp.Y);
		int mid_root=0;
		if(l(sp0.X)<=v-1)mid_root=mrg(mid_root,nwnd(l(sp0.X),v-1,l(sp0.X)));
		if(v+1<=r(sp0.X))mid_root=mrg(mid_root,nwnd(v+1,r(sp0.X),pos[v+1]=v+1));//分裂成至多3个区间
		root=mrg(mrg(mrg(sp.X,mid_root),sp0.Y),nwnd(v,v,pos[v]=++mx_pos));//mrg回去 
	}
	int xth(int x,int p=-1)/*排名为x的用户编号,往下走一遍*/{~p||(p=root);
		if(real_sz(lson(p))+1<=x&&x<=real_sz(lson(p))+r(p)-l(p)+1)return l(p)+x-real_sz(lson(p))-1;
		if(x<=real_sz(lson(p)))return xth(x,lson(p));
		return xth(x-real_sz(lson(p))-(r(p)-l(p)+1),rson(p));
	}
	void dfs(int p=-1)/*调试用*/{~p||(p=root);
		if(!p)return;
		sprdwn(p);
		dfs(lson(p));
		printf("node#%d:[%d,%d] pos=%d\n",p,l(p),r(p),pos(p));
		dfs(rson(p));
	}
}trp;
int main(){
	cin>>n>>qu;
	int lasans=0;
	trp.init();//fhq-Treap初始化 
	while(qu--){
		int tp,x,y;
		cin>>tp>>x;
		switch(tp){
		case 1:
			cin>>y;x-=lasans;y-=lasans;
			cout<<(lasans=trp.real_pos(x))<<"\n";
			trp.chg(x,y);
			break;
		case 2:
			x-=lasans;
			cout<<(lasans=trp.real_pos(x))<<"\n";
			trp.mv_top(x);
			break;
		case 3:
			x-=lasans;
			cout<<(lasans=trp.real_pos(x))<<"\n";
			trp.mv_btm(x);
			break;
		default:
			x-=lasans;
			cout<<(lasans=trp.xth(x))<<"\n";
		}
//		trp.dfs();
	}
	return 0;
}
posted @ 2020-04-19 14:14  ycx060617  阅读(196)  评论(1编辑  收藏  举报