【洛谷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;
}
posted @ 2021-02-16 00:30  stoorz  阅读(69)  评论(0编辑  收藏  举报