「解題報告」HDU6756 Finding a MEX


傳送 🚪

題意

一張 \(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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM