传送门:
题意:
有一个 n n n个结点, m m m条边的无向带权图。现在你想要在加 p p p条边,使得其能够划分成 q q q个连通块。对于加的边:
- 如果两个结点之前不在同一个连通块里,则加的边的边权为: min ( 1 0 9 , S + 1 ) \min(10^9,S+1) min(109,S+1), S S S是两个结点分别所在的连通块的权值和
- 如果两个结点在同一个连通块中,则加的边的边权为: 1000 1000 1000
现在问你是否能够构成 q q q个连通块,使得权值最小。
题目分析:
很显然,这道题目中设计无向图的连通性问题,因此我们优先考虑使用并查集进行维护。
首先需要知道,若连通块 a a a与连通块 b b b之间加一条连边,则连通块 a a a, b b b随即成为一个新的连通块。因此,只要我们能够做到在两个不同的连通块中连边,就可以使得总的联通数减少。而因为这个过程不可逆,因此,倘若当前的连通块的个数小于所要求的数量 q q q则一定无法满足条件。
其次,因为要求最终的图的权值最小,而在不同连通块之间加边的的边权取决于他们的权值和。因此,我们不难分析出要优先使用方案 1 1 1构造出 q q q个连通块后再用方案 2 2 2处理剩下的边。
而对于方案 1 1 1,显然最优的方案是两个较小的连通块之间连一条边,因此我们只需要用一个小根堆去维护一下这个某个连通块的权值即可。
而对于连通块的个数,连通块的权值我们可以用并查集维护。
之后就大力码就好了。
#include <bits/stdc++.h>
#define maxn 100005
using namespace std;
typedef long long ll;
typedef pair<ll,int>pll;
int INF=1e9;
int Far[maxn],sizee[maxn],cnt=0;
ll len[maxn];
int Find_F(int x){
if(Far[x]==x) return x;
else return Find_F(Far[x]);
}
void unite(int x,int y){//并查集分别维护集合的个数和整个集合中的权值和
x=Find_F(x),y=Find_F(y);
if(x==y) return ;
if(sizee[x]>=sizee[y]) Far[y]=x,sizee[x]+=sizee[y],len[x]+=len[y];
else Far[x]=y,sizee[y]+=sizee[x],len[y]+=len[x];
cnt--;
}
bool same(int x,int y){
if(Find_F(x)==Find_F(y)) return true;
else return false;
}
vector<int>vec1,vec2;
int main()
{
int n,m,p,q;
scanf("%d%d%d%d",&n,&m,&p,&q);
cnt=n;
for(int i=1;i<=n;i++) Far[i]=i,sizee[i]=1,len[i]=0;
for(int i=1;i<=m;i++){
int a,b,l;
scanf("%d%d%d",&a,&b,&l);
if(!same(a,b)) unite(a,b);
len[Find_F(a)]+=l;
}
if(cnt<q){//当前剩余连通块个数小于需要构成的连通块数则不符合条件
puts("NO");
return 0;
}
priority_queue<pll,vector<pll>,greater<pll> >que;//小根堆
for(int i=1;i<=n;i++){//小根堆维护连通块的权值
if(i==Find_F(i)) que.push(pll(len[i],i));
}
while(cnt>q){
int v=que.top().second;que.pop();
int u=que.top().second;que.pop();
unite(v,u);
int Farv=Find_F(v);
ll add=min(ll(INF),len[Farv]+1ll);
len[Farv]+=add;
que.push(pll(len[Farv],Farv));
vec1.push_back(u),vec2.push_back(v);
}
if(vec1.size()>p){
puts("NO");
return 0;
}
if(vec1.size()<p){
int pr=1;
while(pr<=n&&(Find_F(pr)!=pr)|| sizee[pr]<2) pr++;
if(pr>n){
puts("NO");
return 0;
}
int i=1;
while(Find_F(i)!=pr) i++;
int j=i+1;
while(Find_F(j)!=pr) j++;
while(vec1.size()<p) vec1.push_back(i),vec2.push_back(j);
}
puts("YES");
for(int i=0;i<vec1.size();i++){
printf("%d %d\n",vec1[i],vec2[i]);
}
return 0;
}
探讨了在无向带权图中通过增加特定数量的边来划分连通块的问题,旨在达到最少总权值的同时满足指定连通块数量的要求。文章详细分析了算法思路,包括使用并查集和小根堆进行优化。
704

被折叠的 条评论
为什么被折叠?



