CodeForces 362D

7/29 G. Fools and Foolproof Roads 362D

题意:对于所给的n点m边的无向图,在图中需要增加p条边实现让图中的连通分量恰好为q,如果可行输出NO,不可以输出YES和添加P条边的方案。其中需要满足所加的边权值最小的情况,每两点间可以有多条边,对于新加的边的权值为min(1e9,A+B+1),A,B为另外两个联通分量的值之和。

思路:看到连通分量就可以知道使用并查集了,用并查集可以找到有多少个这样的联通分量,后面涉及到边的问题就采用set来存储权值,保证每次使用最少的,然后用vector来存储我要输出相连的两个端点部分就可以了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <vector>
#include <iostream>
using namespace std;

typedef __int64 ll;
int n,m,p,q;
int tot;    //记录联通分量个数
const int MAXN=1e5+5;
struct Edge
{
    int u,v;
    ll dis;

} edge[MAXN*2];
set<int> st;    //用于存储每个连通分量中的fa的标志值
set<int>::iterator it;
int f[MAXN];
struct Node
{
    int fa;
    ll val;

    Node(int ff=0,ll vv=0)
    {
        fa=ff;
        val=vv;
    }
    // 对val进行由小到大的排序
    bool operator< (const Node &x)const
    {
        if(val==x.val) return fa>x.fa;
        return val<x.val;
    }
} node[MAXN];
set<Node> myset;    //存储对应的fa和每个连通分量内部的权值和
set<Node>::iterator iit;
vector <int> L,R;   //存储端点
void init()
{
    st.clear();
    myset.clear();
    for(int i=1; i<=n; i++) f[i]=i;
    L.clear();
    R.clear();
    tot=0;
}

int findx(int x)
{
    if(x==f[x]) return x;
    return f[x]=findx(f[x]);
}
void Union(int x,int y)
{
    int tx=findx(x);
    int ty=findx(y);
    if(tx!=ty)
    {
        if(tx<ty) swap(tx,ty);
        f[tx]=ty;   //把大的并入到小的里面,方便最后的输出
    }
}


void add(int u,int v,ll dis)
{
    Edge e= {u,v,dis};
    edge[tot++]=e;
}

void solve()
{
    iit=myset.begin();
    Node x=*iit;
    myset.erase(iit);
    iit=myset.begin();
    Node y=*iit;
    myset.erase(iit);
    Union(x.fa,y.fa);//找到set中最小的两个部分进行合并
    ll Val=min((ll)1000000000,x.val+y.val+1);
    Node z=Node(findx(x.fa),x.val+y.val+Val);
    myset.insert(z);
    L.push_back(x.fa);
    R.push_back(y.fa);
    add(x.fa,y.fa,1);//!!!一定要加上,当m=0时的反例,最后合并点之后的输出
}

ll cnt[MAXN];
int main()
{
    while(~scanf("%d %d %d %d",&n,&m,&p,&q))
    {
        init();
        int u,v;
        ll dis;
        for(int i=0; i<m; i++)
        {
            scanf("%d %d %lld",&u,&v,&dis);
            add(u,v,dis);
            Union(u,v);
        }
        for(int i=1; i<=n; i++) findx(i); //初始计算出f[i]
        for(int i=1; i<=n; i++) st.insert(f[i]);
        for(int i=0; i<tot; i++)
            cnt[f[edge[i].u]]+=edge[i].dis; //找到这个集合内的权值和

        if(st.size()<q) //当连通分量数不满足要求
        {
            puts("NO");
            continue;
        }
        if(st.size()==q)
        {
            if(p>0&&tot==0)
            {
                puts("NO");
                continue;
            }
            else
            {
                puts("YES");
                while(p--)
                    cout<<edge[0].u<<" "<<edge[0].v<<endl;
            }
            continue;
        }
        int need =st.size()-q;  //需要加入的边
        if(need>p)
        {
            puts("NO");
            continue;
        }
        p-=need;
        for(it=st.begin(); it!=st.end(); it++) myset.insert(Node(*it,cnt[*it]));
        while(need--) solve();

        puts("YES");
        for(int i=0; i<L.size(); i++) cout<<L[i]<<" "<<R[i]<<endl;
        while(p--)  //其余的情况下只需要在任意一个联通分量的内部找到两个点相连就可以了。
            cout<<edge[0].u<<" "<<edge[0].v<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值