【洛谷P5631】最小mex生成树
线段树分治+可撤销并查集
题目大意:
给定一张无向图连通图,带边权。
求一棵生成树,满足其边权的 mex 最小。
求最小 mex。
mex 为最小的未出现的自然数。
题解:
当 mex 为 $w$ 的时候,边权为 $w$ 的边一定不能出现。
那么边权为 $w$ 的边,只能在 mex 为 $[0,w-1]$ 或 $[w+1,+\infty]$ 时出现。
线段树分治,对于边权为 $w$ 的边,在 $[0,w-1]$ 和 $[w+1,+\infty]$ 处插入这条边。
然后遍历,用并查集维护连通状态。当前线段树区间为 $[l,r]$ 时,满足使用的边中一定没出现过边权在 $[l,r]$ 之间的边。由于我们遍历线段树时,先遍历左边再遍历右边,任意时刻,连通块个数为 $1$ 时,即可得到最小 mex。
时间复杂度 $O(m\log n\log w)$,空间复杂度 $O(m\log w)$。
Code:
#include<cstdio>
#include<cctype>
#include<vector>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int M=1e6+5,N=1e5+5;
struct edge{
int u,v,w;
}e[M<<1];
vector<int>vec[N<<2];
char buf[(int)2e8],*ss=buf;
inline int init(){buf[fread(buf,1,(int)2e8-1,stdin)]='\n';fclose(stdin);return 0;}
const int __START__=init();
inline int readint(){
int d=0;
while(!isdigit(*ss))++ss;
while(isdigit(*ss))d=d*10+(*ss++^'0');
return d;
}
int n,m,mxb,fa[M],sz[M],bl,sta[M<<1],top;
inline int find(int x){while(x!=fa[x])x=fa[x];return x;}
void add(int l,int r,int o,const int&L,const int&R,const int&id){
if(L<=l&&r<=R)vec[o].push_back(id);else{
const int mid=l+r>>1;
if(L<=mid)add(l,mid,o<<1,L,R,id);
if(mid<R)add(mid+1,r,o<<1|1,L,R,id);
}
}
void solve(int l,int r,int o){
const int TOP=top,BL=bl;
for(int id:vec[o]){
int x=find(e[id].u),y=find(e[id].v);
if(x!=y){
if(sz[x]<sz[y])swap(x,y);
sz[x]+=sz[y],fa[y]=x;
sta[++top]=y;
--bl;
}
}
if(bl==1){
printf("%d\n",l);
exit(0);
}
const int mid=l+r>>1;
if(l<r)solve(l,mid,o<<1),solve(mid+1,r,o<<1|1);
while(top!=TOP){
int y=sta[top--];
sz[fa[y]]-=sz[y],fa[y]=y;
}
bl=BL;
}
int main(){
n=readint(),m=readint();
for(int i=1;i<=m;++i){
e[i].u=readint(),e[i].v=readint();
mxb=max(mxb,e[i].w=readint());
}
for(int i=1;i<=m;++i){
if(e[i].w)
add(0,mxb,1,0,e[i].w-1,i);
if(e[i].w<mxb)
add(0,mxb,1,e[i].w+1,mxb,i);
}
for(int i=1;i<=n;++i)fa[i]=i,sz[i]=1;
bl=n;
solve(0,mxb,1);
printf("%d\n",mxb+1);
return 0;
}