「解题报告」HDU6756 Finding a MEX

醉酒当歌 提交于 2020-07-28 08:32:32

传送 🚪

题意

一张 \(n\) 个点 \(m\) 条边的无向图 \(G = (V,E)\) .

每个点有点权 \(A_i\).

对于每个点 \(u\), 定义集合 \(S_u = \{ A_v | (u,v) \in E \}.\)

定义 \(\rm MEX_u\) 为集合 \(S_u\) 中不存在的最小非负整数.

\(q\) 个指令,

  1. 1 u x : 把 \(A_u\) 改为 \(x\).
  2. 2 u : 询问 \(\rm MEX_u\).

数据范围

$ 1 \le n \le 10^5,\ 1 \le m \le 10^5,\ 1 \le q \le 10^5,\ 0 \le A_i \le 10^9.$


思路

总思路 : 根号分治.

前置知识 : 树状数组二分.

树状数组求 \(\rm MEX\)

首先需要知道, 对于 \(\rm MEX_u\), 我们可以用树状数组 \(O(\log n)\) 的求出.

为了方便描述以及便于树状数组上求解, 我们把所有点权 \(+1\), 并把 \(\rm MEX\) 重定义为集合中 最小的正整数.

\(\rm MEX\), 朴素的思路是把集合中的数放进桶里, 然后找桶中的第一个空位. 这个思路也可以描述为 : 找到第一个满足前缀和 \(sum_i \not = i\) 的位置 \(i\).

而前缀和 \(sum_i\) 我们可以用树状数组维护, 然后再在树状数组上二分查找答案就行了.

树状数组二分的思路和线段树二分差不多, 都是一级一级往下找.

具体来说, 初始时把 \(l\) 设为 0, \(r\) 设为二分范围的最大值, 每次 \(mid\) 的值是 \(l\) 加上 \(r-l\) 内最大的 \(2\) 的若干次幂. 因为树状数组中每个节点的 "管理范围" 都是 \(2\) 的若干次幂, 所以按照上述取值方法可以保证每次 \(mid\) 都严格落在 树状数组下一层级中 一个点的 "管理范围" 的最右端, 保证了二分过程中 一级一级 往下找. 时间复杂度为 \(O(\log n)\).

根号分治

由于 \(m \le 10^5\) ($ \sqrt{10^5} \approx 317$), 所以度数大于等于 \(317\) 的点不会超过 \(317\) 个. 我们把这些节点称作大节点, 其他点称作小节点.

对于所有小节点, 我们可以直接暴力遍历它相连的所有节点, 求出 \(\rm MEX\), 单次的时间复杂度为 \(O(\sqrt{n})\).

而对于所有大节点, 我们考虑用树状数组来求出 \(\rm MEX\).

具体来说, 在修改节点 \(u\) 的权值时, 我们遍历它相连的所有大节点 \(x\), 在 \(x\) 的(权值)树状数组上进行相应的修改. 由于大节点的个数不会超过 \(\sqrt{n}\), 所以时间复杂度为 \(O(\sqrt{n} \log{n})\).

然后因为一个点的 \(\rm MEX\) 不会超过它的度数, 所以树状数组的大小只用开到 \(n\) 就行了.

最后总时间复杂度为 \(O(q\sqrt{n}\log{n})\).


代码

#include<bits/stdc++.h>

using namespace std;

#define pb push_back
#define sz(x) (int)(x).size()

const int _=400+7;
const int __=1e5+7;

int n,m,sq,num[__],val[__],de[__],id[__],sz[_],b[_][__],c[_][__],cnt,q[_];
vector<int> to[__][2];
struct edge{
	int u,v;
}e[__];

int gi(){
	int x=0; char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x;
}

void Pre(){
	num[2]=1; for(int i=3;i<=1e5;i++) num[i]= i>(num[i-1]<<1) ?(num[i-1]<<1) :num[i-1];
}

void Add(int u,int x,int v){
	for(int i=x;i<=sz[id[u]];i+=i&(-i))
		c[id[u]][i]+=v;
}

void Init(){
	n=gi(),m=gi(),sq=sqrt(n);
	for(int i=1;i<=n;i++){ val[i]=gi(); ++val[i]; }
	for(int i=1;i<=m;i++){
		e[i]={gi(),gi()};
		++de[e[i].u],++de[e[i].v];
	}
	for(int i=1;i<=n;i++)
		if(de[i]>=sq){ id[i]=++cnt; sz[id[i]]=de[i]; }
	for(int i=1;i<=m;i++){
		to[e[i].u][0].pb(e[i].v);
		to[e[i].v][0].pb(e[i].u);
		if(id[e[i].u]) to[e[i].v][1].pb(e[i].u);
		if(id[e[i].v]) to[e[i].u][1].pb(e[i].v);
	}

	for(int i=1;i<=n;i++)
		for(auto j:to[i][1])
			if(val[i]<=de[j]){
				if(!b[id[j]][val[i]]) Add(j,val[i],1);
				++b[id[j]][val[i]];
			}
}

void Modify(int u,int x){
	for(auto v:to[u][1]){
		if(val[u]<=de[v]){
			--b[id[v]][val[u]];
			if(!b[id[v]][val[u]]) Add(v,val[u],-1);
		}
		if(x<=de[v]){
			if(!b[id[v]][x]) Add(v,x,1);
			++b[id[v]][x];
		}
	}
	val[u]=x;
}

void Calc(int u){
	if(!id[u]){
		int x=1;
		for(auto v:to[u][0])
			if(val[v]<=de[u]){
				b[0][val[v]]=1;
				q[++q[0]]=val[v];
				while(b[0][x]) ++x;
			}
		printf("%d\n",x-1);
		for(int i=1;i<=q[0];i++) b[0][q[i]]=0;
		q[0]=0;
	}
	else{
		int l=0,r=sz[id[u]],x=r;
		while(l<r-1){
			int mid=l+num[r-l];
			if(c[id[u]][mid]<mid-l) x=r=mid;
			else l=mid;
		}
		printf("%d\n",x-1);
	}
}

void Run(){
	int Q=gi(),ty,u,x;
	while(Q--){
		ty=gi(),u=gi();
		if(ty==1){ x=gi(); Modify(u,x+1); }
		else Calc(u);
	}

	for(int i=1;i<=n;i++){
		to[i][0].clear(),to[i][1].clear();
		de[i]=1; id[i]=0;
	}
	for(int i=1;i<=cnt;i++)
		for(int j=1;j<=sz[i];j++)
			b[i][j]=c[i][j]=0;
	cnt=0;
}

int main(){
#ifndef ONLINE_JUDGE
	freopen("1.in","r",stdin);
	freopen("x.out","w",stdout);
#endif
	int T=gi();
	Pre();
	while(T--){
		Init();
		Run();
	}
	return 0;
}


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!