ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

HDOJ 6756 - Finding a MEX

2020-07-23 14:31:13  阅读:271  来源: 互联网

标签:sz int lim 复杂度 6756 Finding mathrm MEX define


前天的多校题。朝鲜就是毒瘤,签到题都这么难/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;
}

标签:sz,int,lim,复杂度,6756,Finding,mathrm,MEX,define
来源: https://www.cnblogs.com/ycx-akioi/p/HDOJ-6756.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有