【2020杭電多校round1 1006】HDU6756 Finding a MEX


題目大意

題目鏈接

給定一張\(n\)個點\(m\)條邊的無向圖\(G=(V,E)\)。每個點\(u\)有一個點權\(a_u\)。令\(S_u=\{a_v|(u,v)\in E\}\)。令\(F_u=\operatorname{mex}(S_u)\),其中\(\operatorname{mex}\)表示最小的、在集合里沒出現過的,非負整數。

接下來會給出\(q\)次操作,操作有兩種:

  • \(1\ u\ x\),把點\(u\)的點權改成\(x\),也就是令\(a_u:=x\)
  • \(2\ u\),查詢\(F_u\)的值。

請你對所有操作\(2\)輸出答案。

數據范圍:\(1\leq n,m\leq 10^5\)\(0\leq a_u\leq 10^9\)\(1\leq q\leq 10^5\)

本題題解

設置一個值\(B\)。按點的度數\(>B\)\(\leq B\),將點分為“大點”和“小點”。每個小點的鄰居數不超過\(B\),大點的總數不超過\(\frac{n}{B}\),這對我們來說是很好的性質。

對於一次修改操作,如果被修改的點是小點,則可以直接暴力枚舉它的所有鄰居,進行修改。如果被修改的點是大點,則什么也不做(有點像線段樹的懶標記,先放着,等查詢的時候再說)。

查詢時,所有小點對當前點的影響已經維護好了。考慮大點的影響,暴力枚舉所有與當前點相鄰的大點,如果它被修改過,更新當前點的\(F\)值即可。因為大點的總數只有\(O(\frac{n}{B})\)個,所以一次操作最多做\(O(\frac{n}{B})\)次修改。

那么問題就轉化為,如何維護出每個點的\(S\)集合,支持加入一個值、刪除一個值、查詢\(\operatorname{mex}\)。如果用動態開點線段樹(給圖上每個點都開一棵),則一次修改時間復雜度是\(O(\log a)\)的,一次查詢時間復雜度是\(O(1)\)的。空間復雜度理論上最大甚至高達\(O((n+q)(B+\frac{n}{B})\log a)\)。如果\(B\)\(\sqrt{n}\),則時間、空間復雜度都是\(O(n\sqrt{n}\log a)\)的。雖然實際上可以AC,但還可以繼續優化。

我們發現,一個點的\(F_u\)值(也就是\(\operatorname{mex}(S_u)\)),不會超過\(\operatorname{deg}(u)\)(因為\(S_u\)的大小不超過\(\operatorname{deg}(u)\))。所以線段樹的值域只需要開到\(n\),時、空復雜度優化為\(O(n\sqrt{n}\log n)\)

進一步優化,考慮不用線段樹。我們發現,線段樹的特點是,修改較慢,查詢很快(是\(O(1)\)的)。但是我們恰恰需要較快的修改(因為修改操作需要進行\(O((n+q)\sqrt{n})\)次),而查詢則可以慢一點(因為只會做\(O(q)\)次查詢)。

於是考慮用分塊替代線段樹。對值域分塊,用數組記錄每個值的出現次數,和每個塊內有多少種不同的值,這樣修改是\(O(1)\)的。因為每個點值域是\(\operatorname{deg}(u)\)的,所以記錄這些需要的總空間就是\(\sum \operatorname{deg}(u)=O(m)\)的。查詢時,暴力枚舉所有的塊,時間復雜度是\(O(\sqrt{\operatorname{deg}(u)})=O(\sqrt{n})\)的。於是,我們通過分塊,犧牲了查詢的復雜度,實現了\(O(1)\)修改。

總時間復雜度降為\(O(n+q\sqrt{n})\)

參考代碼:

//problem:1006
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

template<typename T>inline void ckmax(T& x,T y){x=(y>x?y:x);}
template<typename T>inline void ckmin(T& x,T y){x=(y<x?y:x);}

const int B=300;
const int MAXN=1e5;

int n,m,q,a[MAXN+5],deg[MAXN+5];
bool is_big[MAXN+5];
vector<int>G[MAXN+5],cnt[MAXN+5],tag[MAXN+5];
vector<pii>big_nb[MAXN+5];//big neighbour

inline void ins(int u,int x){
	ckmin(x,deg[u]);
	cnt[u][x]++;
	if(cnt[u][x]==1)
		tag[u][x/B]++;
}
inline void del(int u,int x){
	ckmin(x,deg[u]);
	cnt[u][x]--;
	if(cnt[u][x]==0){
		tag[u][x/B]--;
	}
}
int query_mex(int u){
	for(int i=0;i<=deg[u];i+=B){
		int j=min(i+B-1,deg[u]);
		if(tag[u][i/B] < j-i+1){
			for(int k=i;k<=j;++k)
				if(!cnt[u][k])
					return k;
			throw;
		}
	}
	throw;
}

void solve_case(){
	cin>>n>>m;
	for(int i=1;i<=n;++i){
		deg[i]=0;
		is_big[i]=false;
		vector<int>().swap(G[i]);
		vector<int>().swap(cnt[i]);
		vector<int>().swap(tag[i]);
		vector<pii>().swap(big_nb[i]);
		//相當於 v.clear()
	}
	for(int i=1;i<=n;++i)
		cin>>a[i];
	for(int i=1;i<=m;++i){
		int u,v; cin>>u>>v;
		G[u].pb(v); G[v].pb(u);
		deg[u]++; deg[v]++;
	}
	for(int i=1;i<=n;++i){
		is_big[i]=(deg[i]>B);
		cnt[i].resize(deg[i]+1);
		tag[i].resize(deg[i]/B+1);
	}
	for(int u=1;u<=n;++u){
		for(int i=0;i<SZ(G[u]);++i){
			int v=G[u][i];
			if(is_big[v])
				big_nb[u].pb(mk(v,a[v]));
			ins(u,a[v]);
		}
	}
	cin>>q;
	for(int tq=1;tq<=q;++tq){
		int op; cin>>op;
		if(op==1){
			int u,new_a; cin>>u>>new_a;
			if(is_big[u]){
				a[u]=new_a;
			}
			else{
				for(int i=0;i<SZ(G[u]);++i){
					int v=G[u][i];
					del(v,a[u]);
					ins(v,new_a);
				}
				a[u]=new_a;
			}
		}
		else{
			int u; cin>>u;
			for(int i=0;i<SZ(big_nb[u]);++i){
				int v=big_nb[u][i].fi;
				int old_a=big_nb[u][i].se;
				if(old_a != a[v]){
					del(u,old_a);
					ins(u,a[v]);
					big_nb[u][i].se=a[v];
				}
			}
			cout<<query_mex(u)<<endl;
		}
	}
}
int main(){
	int T;cin>>T;while(T--){
		solve_case();
	}
	return 0;
}


免責聲明!

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



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