Codeforces 362D (并查集)

传送门:

题意:

有一个nn个结点,mm条边的无向带权图。现在你想要在加pp条边,使得其能够划分成qq个连通块。对于加的边:

  • 如果两个结点之前不在同一个连通块里,则加的边的边权为:min(109,S+1)\min(10^9,S+1)SS是两个结点分别所在的连通块的权值和
  • 如果两个结点在同一个连通块中,则加的边的边权为:10001000

现在问你是否能够构成qq个连通块,使得权值最小。

题目分析:

很显然,这道题目中设计无向图的连通性问题,因此我们优先考虑使用并查集进行维护。

首先需要知道,若连通块aa与连通块bb之间加一条连边,则连通块aabb随即成为一个新的连通块。因此,只要我们能够做到在两个不同的连通块中连边,就可以使得总的联通数减少。而因为这个过程不可逆,因此,倘若当前的连通块的个数小于所要求的数量qq则一定无法满足条件。

其次,因为要求最终的图的权值最小,而在不同连通块之间加边的的边权取决于他们的权值和。因此,我们不难分析出要优先使用方案11构造出qq个连通块后再用方案22处理剩下的边。

而对于方案11,显然最优的方案是两个较小的连通块之间连一条边,因此我们只需要用一个小根堆去维护一下这个某个连通块的权值即可。

而对于连通块的个数,连通块的权值我们可以用并查集维护。

之后就大力码就好了。

#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;
}
posted @ 2019-02-17 14:19  ChenJr  阅读(323)  评论(0编辑  收藏  举报