HDOJ 6756

◇◆丶佛笑我妖孽 提交于 2020-08-04 09:38:15

前天的多校题。朝鲜就是毒瘤,签到题都这么难/kk

HDOJ题目页面传送门

给定一个无向图\(G=(V,E),|V|=n,|E|=m\),每个点有点权\(a_i\),设\(S_i=\{a_j\mid (i,j)\in E\}\),支持\(2\)\(q\)次操作:

  1. \(\texttt1\ x\ y\):令\(a_x=y\)
  2. \(\texttt2\ x\):求\(\mathrm{mex}(S_x)\)

\(n,m\in\left[1,10^5\right],a_i\in\left[0,10^9\right]\)。本题多测,最多有\(10\)组数据。\(\mathrm{TL}=3\mathrm s\)

以下认为\(n,m\)同阶,复杂度里用\(n\)表示。

对于无向图上的询问题,一般有一个套路:根号分治(还是听yjz讲课才会的)。考虑设一个界限\(lim\),将度数\(\leq lim\)的都设为小点,其他都是大点。不难发现,小点度数上限是\(\mathrm O(lim)\),大点个数上限是\(\mathrm O\!\left(\dfrac n{lim}\right)\)(因为\(\sum\limits_{i=1}^n|\delta(i)|=2m\)),这是两个很好的性质。

于是分成四个需要思考的问题:小点修改,小点查询,大点修改,大点查询。

首先有一条引理:\(\mathrm{mex}(S)=\mathrm O(|S|)\)。证明很容易,当\(S=\{0,1,2,\cdots\}\)时会把\(\mathrm{mex}(S)\)卡最大。注意到对于小点查询,小点\(x\)的度数不会太大,那么\(\mathrm{mex}(S_x)\)也不会太大,是\(\mathrm O(lim)\)级别的,考虑不用对它维护任何东西,直接暴力查。至于咋暴力,在外面开一个全局的\([0,n]\)上的bool数组(点权超过\(n\)可以直接当作\(n\)看,正确性显然),每次小点查询将邻居点权赋上true,然后从头遍历,查完撤销影响。

那么对应的,大点查询的话我们就需要维护一些东西来实现快速查询;而无论是小点修改还是大点修改,都可以修改所有邻居大点(这样的点数量是\(\mathrm O\!\left(\dfrac m{lim}\right)\)级别的)所维护的数据结构。

看到在集合里查东西,很容易想到平衡树,这里使用fhq-Treap。这样,改点权的时候是平衡树正常操作(单点插入+单点删除),\(\mathrm O(\log n)\);查\(\mathrm{mex}\)的时候,可以用类似二分的思想,在当前节点时,如果左子树是满的(即开头到内部到当前节点之间没有一点空隙),就往右子树走,否则往左子树走,时间复杂度与树高成正比,\(\mathrm O(\log n)\)

下面来整理一下各操作的复杂度:

  1. 小点修改:平衡树插入+删除,\(\mathrm O\!\left(\dfrac n{lim}\log n\right)\)
  2. 小点查询:暴力,\(\mathrm O(lim)\)
  3. 大点修改:平衡树插入+删除,\(\mathrm O\!\left(\dfrac n{lim}\log n\right)\)
  4. 大点查询:平衡树查询,\(\mathrm O(\log n)\)

时间复杂度就是\(\mathrm O\!\left(q\left(\dfrac n{lim}\log n+lim\right)\right)\)。根据均值不等式,当\(lim\)\(\sqrt{m\log n}\)的时候最优,为\(\mathrm O\!\left(n\sqrt{n\log n}\right)\)

现场觉得这个复杂度有点悬,常数又大,写好之后交上去,果然T了!这比WA还要狠。后来事实证明不是复杂度/常数的问题,是写挂了。之后TLE->RE->WA->AC,太不容易了……

代码(现场rush的,还有sjc在旁边不停啰嗦,有点丑,见谅):

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
void read(int &x){
	x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
void prt(int x){
	if(x>9)prt(x/10);
	putchar(x%10^48);
}
mt19937 rng(20060617);
const int N=100000;
int n,m,qu;
int a[N+1];
vector<int> nei[N+1];
vector<int> nei_big[N+1];
struct fhq_treap{//平衡树 
	int sz,root;
	struct node{unsigned key;int lson,rson,sz,v,mx;}nd[2*N+1];
	#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 v(p) nd[p].v
	#define mx(p) nd[p].mx
	void init(){
		sz=root=0;
		nd[0]=node({0,0,0,0,0,0});
	}
	void sprup(int p){sz(p)=sz(lson(p))+1+sz(rson(p));mx(p)=max(mx(lson(p)),max(v(p),mx(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-1-sz(lson(p)),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 lss(int v,int p=-1){~p||(p=root);
		if(!p)return 0;
		if(v(p)<v)return sz(lson(p))+1+lss(v,rson(p));
		return lss(v,lson(p));
	}
	int nwnd(int v){return nd[++sz]=node({rng(),0,0,1,v,v}),sz;}
	void insert(int v){
//		cout<<"insert "<<v<<"\n";
		pair<int,int> sp=split(lss(v));
		root=mrg(mrg(sp.X,nwnd(v)),sp.Y);
	}
	void del(int v){
//		cout<<"del "<<v<<"\n";
		pair<int,int> sp=split(lss(v)),sp0=split(1,sp.Y);
		root=mrg(sp.X,sp0.Y);
	}
	int mex(int now=0,int p=-1){~p||(p=root);
//		printf("mex %d %d\n",now,v(p));
		if(!p)return now;
		if(v(p)-now==sz(lson(p)))return mex(v(p)+1,rson(p));
		return mex(now,lson(p));
	}
}trp[100];
int hav[100][N+1];
int big_id[N+1];
bool tmp[N+1];
void mian(){
	read(n);read(m);
	for(int i=1;i<=n;i++)nei[i].clear();
	for(int i=1;i<=n;i++)read(a[i]),a[i]=min(n,a[i]);
	for(int i=1;i<=m;i++){
		int x,y;
		read(x);read(y);
		nei[x].pb(y);nei[y].pb(x);
	}
	int lim=sqrt(m*log2(n));
	for(int i=1;i<=n;i++){
		nei_big[i].clear();
		for(int j=0;j<nei[i].size();j++)
			if(nei[nei[i][j]].size()>lim)nei_big[i].pb(nei[i][j]);
	}
	int now=0;
	for(int i=1;i<=n;i++)if(nei[i].size()>lim){
		trp[big_id[i]=now++].init();
		for(int j=0;j<=n;j++)hav[big_id[i]][j]=0;
		for(int j=0;j<nei[i].size();j++){
			if(!hav[big_id[i]][a[nei[i][j]]])trp[big_id[i]].insert(a[nei[i][j]]);
			hav[big_id[i]][a[nei[i][j]]]++;
//			cout<<big_id[i]<<"!\n";
		}
	}
	read(qu);
	while(qu--){
		int tp,x,y;
		read(tp);read(x);
		if(tp==1){//修改 
			read(y);
			y=min(n,y);
			for(int i=0;i<nei_big[x].size();i++){
				int z=nei_big[x][i];
				hav[big_id[z]][a[x]]--;
//				cout<<big_id[z]<<"!\n";
				if(!hav[big_id[z]][a[x]])trp[big_id[z]].del(a[x]);
				if(!hav[big_id[z]][y])trp[big_id[z]].insert(y);
				hav[big_id[z]][y]++;
			}
			a[x]=y;
		}
		else{
			if(nei[x].size()<=lim){//小点查询 
				for(int i=0;i<nei[x].size();i++)tmp[a[nei[x][i]]]=true;
				for(int i=0;;i++)if(!tmp[i]){prt(i);putchar('\n');break;}
				for(int i=0;i<nei[x].size();i++)tmp[a[nei[x][i]]]=false;
			}
			else prt(trp[big_id[x]].mex()),putchar('\n');//大点查询 
		}
	}
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}

后来看到隔壁dls的题解,发现竟然还有\(\mathrm O(n\sqrt n)\)的算法………………

回顾上面整理的各操作的复杂度,发现一个现象,平衡树查询的次数只有\(\mathrm O(q)\),而平衡树插入+删除的次数有\(\mathrm O(q\dfrac n{lim})\),这两个操作复杂度却一样。那我们是不是可以牺牲一下查询的复杂度,来实现插入+删除复杂度的更优呢?饥渴的人们想要把插入+删除弄成\(\mathrm O(1)\),而能有操作复杂度为\(\mathrm O(1)\)的数据结构并不多,比较典型的就是万能的分块。

考虑基于值域分块,每块\(sz1\)个数,既然要\(\mathrm O(1)\),插入+删除的时候只能直接改一下就得走人;而对于查询,考虑找到第一个不满的块,再在里面暴力找,这样看来,我们得维护每一块内有的元素的个数,为了实现这个的维护,还得维护一个bool数组,这些都是可以\(\mathrm O(1)\)修改的,于是查询复杂度\(\mathrm O\!\left(sz1+\dfrac n{sz1}\right)\)。为了最优,\(sz1\)\(\sqrt n\)。此时不难发现,总复杂度已经优化到\(\mathrm O(n\sqrt n)\)了……

又是根号分治又是分块,这不是lxl的作风么/jk

代码(在原来代码上魔改的):

#include<bits/stdc++.h>
using namespace std;
#define mp make_pair
#define X first
#define Y second
#define pb push_back
void read(int &x){
	x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
void prt(int x){
	if(x>9)prt(x/10);
	putchar(x%10^48);
}
mt19937 rng(20060617);
const int N=100000,DB_SZ1=333,DB_SZ=DB_SZ1;
int n,m,qu;
int a[N+1];
vector<int> nei[N+1];
vector<int> nei_big[N+1];
struct dvdblk{//分块 
	int sz,sz1;
	struct block{int l,r,sz;bool hav[DB_SZ1];}blk[DB_SZ];
	#define l(p) blk[p].l
	#define r(p) blk[p].r
	#define sz(p) blk[p].sz
	#define hav(p) blk[p].hav
	void bldblk(int p,int l,int r){
		l(p)=l;r(p)=r;sz(p)=0;memset(hav(p),0,sizeof(hav(p)));
	}
	void init(){
		sz1=sqrt(n);
		sz=(n+sz1)/sz1;
		for(int i=1;i<=sz;i++)bldblk(i,(i-1)*sz1,min(n,i*sz1-1));
	}
	void insert(int v){
		int p=(v+sz1)/sz1;
		hav(p)[v-l(p)]=true;
		sz(p)++;
	}
	void del(int v){
		int p=(v+sz1)/sz1;
		hav(p)[v-l(p)]=false;
		sz(p)--;
	}
	int mex(){
		for(int i=1;i<=sz1;i++)if(sz(i)<r(i)-l(i)+1){
			for(int j=l(i);;j++)if(!hav(i)[j-l(i)])return j;
		}
	}
}db[333];
int hav[333][N+1];
int big_id[N+1];
bool tmp[N+1];
void mian(){
	read(n);read(m);
	for(int i=1;i<=n;i++)nei[i].clear();
	for(int i=1;i<=n;i++)read(a[i]),a[i]=min(n,a[i]);
	for(int i=1;i<=m;i++){
		int x,y;
		read(x);read(y);
		nei[x].pb(y);nei[y].pb(x);
	}
	int lim=sqrt(m);
	for(int i=1;i<=n;i++){
		nei_big[i].clear();
		for(int j=0;j<nei[i].size();j++)
			if(nei[nei[i][j]].size()>lim)nei_big[i].pb(nei[i][j]);
	}
	int now=0;
	for(int i=1;i<=n;i++)if(nei[i].size()>lim){
		db[big_id[i]=now++].init();
		for(int j=0;j<=n;j++)hav[big_id[i]][j]=0;
		for(int j=0;j<nei[i].size();j++){
			if(!hav[big_id[i]][a[nei[i][j]]])db[big_id[i]].insert(a[nei[i][j]]);
			hav[big_id[i]][a[nei[i][j]]]++;
//			cout<<big_id[i]<<"!\n";
		}
	}
	read(qu);
	while(qu--){
		int tp,x,y;
		read(tp);read(x);
		if(tp==1){//修改 
			read(y);
			y=min(n,y);
			for(int i=0;i<nei_big[x].size();i++){
				int z=nei_big[x][i];
				hav[big_id[z]][a[x]]--;
//				cout<<big_id[z]<<"!\n";
				if(!hav[big_id[z]][a[x]])db[big_id[z]].del(a[x]);
				if(!hav[big_id[z]][y])db[big_id[z]].insert(y);
				hav[big_id[z]][y]++;
			}
			a[x]=y;
		}
		else{
			if(nei[x].size()<=lim){//小点查询 
				for(int i=0;i<nei[x].size();i++)tmp[a[nei[x][i]]]=true;
				for(int i=0;;i++)if(!tmp[i]){prt(i);putchar('\n');break;}
				for(int i=0;i<nei[x].size();i++)tmp[a[nei[x][i]]]=false;
			}
			else prt(db[big_id[x]].mex()),putchar('\n');//大点查询 
		}
	}
}
int main(){
	int testnum;
	cin>>testnum;
	while(testnum--)mian();
	return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!