【洛谷P5631】最小mex生成树
题目
题目链接:https://www.luogu.com.cn/problem/P5631
给定 \(n\) 个点 \(m\) 条边的无向连通图,边有边权。
设一个自然数集合 \(S\) 的 \(\text{mex}\) 为:最小的、没有出现在 \(S\) 中的自然数。
现在你要求出一个这个图的生成树,使得其边权集合的 \(\text{mex}\) 尽可能小。
\(n\leq 10^6,m\leq 2\times 10^6,w_i\leq 10^5\)。
思路
很套路的线段树分治。
设 \(\text{lim}=\max(w_i)\)
考虑分治到 \([l,r]\) 的时候把边权在 \([0,l)∪(r,\text{lim}]\) 的边全部加上,用可撤销并查集搞连通块,设 \(cnt\) 表示合并次数,如果 \(cnt=n-1\) 那么说明已经形成一棵最小生成树,输出 \(l\) 即可。
随便写写就行了。每一条边会被加入 \(O(\log \text{lim})\) 次,所以复杂度是 \(O(\text{lim}(\log\text{lim}+\log n))\) 的。
代码
#include <bits/stdc++.h>
#define mp make_pair
using namespace std;
const int N=2000010,W=100010;
int n,m,lim,cnt,father[N],dep[N];
vector<pair<int,int> > e[W];
stack<int> st;
int read()
{
int d=0; char ch=getchar();
while (!isdigit(ch)) ch=getchar();
while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-18,ch=getchar();
return d;
}
int find(int x)
{
return x==father[x]?x:find(father[x]);
}
int merge(int x,int y,bool flag)
{
x=find(x); y=find(y);
if (x==y) return 0;
if (dep[x]<dep[y]) swap(x,y);
father[y]=x; dep[x]+=(dep[x]==dep[y]);
if (flag) st.push(y);
return 1;
}
bool solve(int l,int r)
{
if (cnt==n-1) { printf("%d",l-1); return 1; }
if (l==r) return 0;
int top=st.size(),mid=(l+r)>>1;
for (int i=mid+1;i<=r;i++)
for (int j=0;j<e[i].size();j++)
cnt+=merge(e[i][j].first,e[i][j].second,1);
if (solve(l,mid)) return 1;
while (st.size()>top)
{
int x=st.top(); st.pop();
dep[father[x]]-=(dep[father[x]]==dep[x]); father[x]=x;
cnt--;
}
for (int i=l;i<=mid;i++)
for (int j=0;j<e[i].size();j++)
cnt+=merge(e[i][j].first,e[i][j].second,0);
if (solve(mid+1,r)) return 1;
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1,x,y,z;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
e[z+1].push_back(mp(x,y));
lim=max(lim,z);
}
for (int i=1;i<=n;i++)
father[i]=i;
solve(1,lim+2);
return 0;
}