【洛谷P5633】最小度限制生成树

题目

题目链接:https://www.luogu.com.cn/problem/P5633
给你一个有 \(n\) 个节点,\(m\) 条边的带权无向图,你需要求得一个生成树,使边权总和最小,且满足编号为 \(s\) 的节点正好连了 \(k\) 条边。
\(n\leq 5\times 10^4,m\leq 5\times 10^5,k\leq 100\)

思路

假设我们直接求出了最小生成树,此时最小生成树上有 \(t\) 条边是连接 \(s\) 点的。不妨设 \(t<k\)
如果此时加入一条没被选择的连接 \(s\) 的边,必然会形成一个环,那么我们找到环上权值最大的不连接 \(s\) 的边删去。重复上述过程就可以得到一个有 \(k\) 条边连接 \(s\) 的生成树。
设恰好有 \(i\) 条连接 \(s\) 的边时最小生成树权值和为 \(s_i\),那么我们可以把 \((i,s_i)\) 看做一个二维平面上的点。连接这些点之后必然是一个下凸壳。因为在刚刚删去黑边加入白边的过程中,为了让生成树权值尽量小,一定是选择差值最小的先加入。
于是就可以采用 wqs 二分解决这个问题了。
时间复杂度 \(O(m\log m\log V)\),其中 \(V\) 是边权最大值。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=50010,M=500010;
int n,m,s,k,cnt,maxd,father[N];
ll sum,ans;

struct edge
{
	int u,v,dis;
}e[M];

bool cmp(edge x,edge y)
{
	return x.dis<y.dis;
}

int find(int x)
{
	return x==father[x]?x:father[x]=find(father[x]);
}

void kruskal()
{
	sort(e+1,e+1+m,cmp);
	for (int i=1;i<=m;i++)
	{
		int x=find(e[i].u),y=find(e[i].v);
		if (x!=y)
		{
			if (e[i].u==s || e[i].v==s) cnt++;
			sum+=e[i].dis; father[x]=y; 
		}
	}
}

int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&k);
	for (int i=1;i<=n;i++) father[i]=i;
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].dis);
		if (e[i].u!=s && e[i].v!=s) 
			father[find(e[i].u)]=find(e[i].v),cnt++;
		maxd=max(maxd,e[i].dis);
	}
	if (cnt+k<n-1) { cout<<"Impossible"; return 0; }
	int l=-maxd,r=maxd,mid;
	ans=-1;
	while (l<=r)
	{
		mid=(l+r)>>1;
		for (int i=1;i<=m;i++)
			if (e[i].u==s || e[i].v==s) e[i].dis+=mid;
		cnt=sum=0;
		for (int i=1;i<=n;i++) father[i]=i;
		kruskal();
		if (cnt>=k) l=mid+1,ans=sum-1LL*mid*k;
			else r=mid-1;
		for (int i=1;i<=m;i++)
			if (e[i].u==s || e[i].v==s) e[i].dis-=mid;
	}
	if (ans!=-1) cout<<ans;
		else cout<<"Impossible";
	return 0;
}
posted @ 2021-07-13 21:09  stoorz  阅读(139)  评论(0编辑  收藏  举报