Codeforces Round #212 (Div. 2) D. Fools and Foolproof Roads(联通分量分解+优先队列)

题目链接:https://codeforces.com/problemset/problem/362/D
题目大意:
有 n 个城市,它们之中有 m 座桥(双向)相连(城市可以孤立),要你新建 p 座桥,使得城市能恰好划分为 q 个区域。同属一个区域内的城市必定可达,若两个城市区域不同则不可达。若新建一座桥使得两个区域合并为一,花费为min(1e9, S) + 1.其中S为这两个区域原本含有的桥的权值之和。若在只在一个区域内的城市建桥,花费1000.注意:允许重边,不得有自环。

解题思路:
怎么知道当前城市属于哪个区域呢?我们可以用并查集或者连通分量分解给城市打标号。遂计算每个区域的桥的权值之和。先贪心地把区域数减少至q。若建p座桥仍无法完成任务,输出NO,否则,如果桥有富于,随意挑出一个点数大于2的连通分量,随意挑出其中两点连边,满足题意的数量即可。需要注意的是,当我们要合并一些区域时,没有必要把两个连通分量合并,如果连通分量点数小于2,加一点即可,若大于2无需操作。这样做的目的也是为了在至少有两点能在同一个区域内建边(就是错了这,比赛刚结束才改好,气煞我也!)。

代码如下:

# include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const ll MAX_V = 1e5 + 5;
const ll inf = 1e9;
ll V, m, p, q, x, y, c;
struct node{
    ll to, cost;
};
vector <node> G[MAX_V];
vector <ll> rG[MAX_V];
vector <ll> vs;
vector <ll> SCC[MAX_V];
bool used[MAX_V];
ll cmp[MAX_V];

typedef pair <ll, ll> P;
vector <P> ans;
priority_queue <P, vector<P>, greater<P> > que;

void add_edge(ll from, ll to, ll cost){
    G[from].push_back({to, cost});
    G[to].push_back({from, cost});
    rG[to].push_back(from);
    rG[from].push_back(to);
}

void dfs(ll v){
    used[v] = true;
    for(ll i = 0; i < G[v].size(); ++i)
        if(!used[G[v][i].to]) dfs(G[v][i].to);
    vs.push_back(v);
}

void rdfs(ll v, ll k){
    used[v] = true;
    cmp[v] = k;
    for(ll i = 0; i < rG[v].size(); ++i)
        if(!used[rG[v][i]]) rdfs(rG[v][i], k);
}

ll scc(){
    memset(used, 0, sizeof(used));
    vs.clear();
    for(ll v = 1; v <= V; v++){
        if(!used[v]) dfs(v);
    }
    memset(used, 0, sizeof(used));
    ll k = 0;
    for(ll i = vs.size()-1; i >= 0; --i)
        if(!used[vs[i]])    rdfs(vs[i], k++);
    return k;
}

int main(){
    std::ios::sync_with_stdio(false);
    cin >> V >> m >> p >> q;
    for(ll i = 0; i < m; ++i){
        cin >> x >> y >> c;
        add_edge(x, y, c);
    }

    ll res = scc();
    for(ll i = 1; i <= V; ++i)   //点的标号从1开始
        SCC[cmp[i]].push_back(i);  //SCC放连通分量的点,连通序号从0开始

    while(!que.empty())   que.pop();
    for(ll i = 0; i < res; ++i){  //i是连通分量序号
        ll sum = 0;
        for(ll j = 0; j < SCC[i].size(); ++j){
            for(ll k = 0; k < G[SCC[i][j]].size(); ++k){
                sum += G[SCC[i][j]][k].cost;
            }
        }
        que.push({sum / 2, i});  //i, second是强连通序号
        //tot[i] = sum / 2;
    }

    while(res > q){
        if(p <= 0)    break;
        P p1 = que.top();  que.pop();
        P p2 = que.top();  que.pop();
        que.push({p1.first + p2.first + min(inf, p1.first + p2.first) + 1, p1.second});
        if(SCC[p1.second].size() == 1)   SCC[p1.second].push_back(SCC[p2.second][0]);  //就是这里少了会出错,细节!!!
        if(SCC[p2.second].size() == 1)   SCC[p2.second].push_back(SCC[p1.second][0]);
        ans.push_back({SCC[p1.second][0], SCC[p2.second][0]});
        res--;
        p--;
    }
    if(res != q)
        cout << "NO" << endl;   //桥少了不够建
    else{  //桥多了,但是已经满足q区域
        ll label = -1;
        for(ll i = 0; i < res; ++i){  //有没有点数大于2的分量
            if(SCC[i].size() > 1){
                label = i;
                break;
            }
        }
        if(label == -1 && p)  cout << "NO" << endl;  //全是单点但是还要建桥
        else{
            cout << "YES" << endl;
            for(ll i = 0; i < ans.size(); ++i)
                cout << ans[i].first << ' ' << ans[i].second << endl;
            while(p){
                cout << SCC[label][0] << ' ' << SCC[label][1] << endl;
                p--;
            }
        }
    }

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值