LGP5631 最小mex生成树 学习笔记
LGP5631 最小mex生成树 学习笔记
前言
这东西勉强算线段树分治?
题意简述
给定 \(n\) 点 \(m\) 边的无向联通图。边有权值。定义自然数集合 \(S\) 的 \(\text{mex}\) 为未出现在其中的最小的自然数。
问这个图最小 \(\text{mex}\) 生成树的 \(\text{mex}\) 值。
\(n\le 10^6\),\(m\le 2\times 10^6\),\(w_i\le 10^5\)。
做法解析
一眼,看着就像是个二分、分治、可撤销并查集相关的东西。
思考当答案为 \(x\) 的时候意味着什么?意味着图中所有 \(w_i\neq x\) 的边都可以用,也相当于是可以加入所有 \(w_i\in [1,x)\cap (x,V]\) 的边。我们之所以写成这个形式是方便从分治和可撤销并查集维护边集的角度思考。
我们把对于答案的分治树遍历一遍,优先走左儿子。走左儿子前加入所有右儿子的边,走右儿子前删掉所有右儿子的边并加入所有左儿子的边。递归返回的时候把右儿子的边重新加回来,这样子我们在考虑区间 \([l,r]\) 的时候,可撤销并查集中刚好就加入了 \([1,l)\cap (r,V]\) 的边。这样我们每次递归到叶节点的时候判一下整个图连不连通即可。复杂度 \(O(V\log^2 V)\)。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e6+5,MaxW=1e5+5;
int N,M,X,Y,Z,V;
struct edge{int u,v;};
vector<edge> E[MaxW];
struct RevokaUnionFind{
int n,ufa[MaxN],usz[MaxN],tim;
struct node{int fu,fv,hc;}mem[MaxN];
void init(int x){n=x,tim=0;for(int i=1;i<=n;i++)ufa[i]=i,usz[i]=1;}
int find(int u){return u==ufa[u]?u:ufa[u]=find(ufa[u]);}
void merge(int u,int v){
int fu=find(u),fv=find(v);
if(fu==fv)return;if(usz[fu]<usz[fv])swap(fu,fv);
int hc=usz[fv];ufa[fv]=fu,usz[fu]+=hc,mem[++tim]={fu,fv,hc};
};
void revoke(int p){
for(;tim>p;tim--){
auto [fu,fv,hc]=mem[tim];
ufa[fv]=fv,usz[fu]-=hc;
}
}
bool isaun(){return usz[find(1)]==n;}
}RevUf;
int trg(){return RevUf.tim;}
void solve(int cl,int cr){
if(cl==cr){if(RevUf.isaun())writi(cl),exit(0);return;}
int cmid=(cl+cr)>>1,ctim=trg();
for(int i=cmid+1;i<=cr;i++)for(auto [u,v] : E[i])RevUf.merge(u,v);
solve(cl,cmid);RevUf.revoke(ctim);
for(int i=cl;i<=cmid;i++)for(auto [u,v] : E[i])RevUf.merge(u,v);
solve(cmid+1,cr);RevUf.revoke(ctim);
}
int main(){
readis(N,M);RevUf.init(N);
for(int i=1;i<=M;i++)readis(X,Y,Z),maxxer(V,Z),E[Z].push_back({X,Y});
solve(0,V+1);return 0;
}
浙公网安备 33010602011771号