codeforces-div1-286-B

题目链接 Mr. Kitayuta's Technology


直接说解法:

先把每个有向边当成无向边, 把有变相连接的点当成一个集合里面的, 然后依次处理每个集合(求每个集合就用并查集).


接下来以此求解每个集合, 每个集合就是一个有向图G, 点就是集合内节点, 边就是题目中的 important transportation, 并且这个图G是联通的(在把边当成无向的情况下).

求解的时候分两种情况, 这个有向图G无环 和 有环

1. 无环

结论: 假设这个无环图G有cnt个节点, 那么需要cnt - 1条边.

证明:

(1) cnt - 1条边足够

我们先把这个无环图拓扑排序, 假设排完后节点顺序为 a0, a1, a2 ... a(cnt-1), 其中a0的入度为0

那么我们这样构建一个图, a0->a1->a2->a3...->a(cnt-2)->a(cnt-1), 对于这个图, 肯定满足条件, 所以cnt - 1条边足够

(2) cnt - 1条边是最小的边数

假设存在一个更优的解 ans(ans < cnt-1), 由于现在有cnt个点, 而只有ans条边, 所以不管怎么用这ans个边来构造, 这个图肯定是不联通的(边无向的情况下).

假设现在用ans构造后的图分为了A, B两大块, 其中A没有到B的边且B没有到A的边.

由于原图G本身是联通的, 因此A中肯定应该有边要求到B 或者 B中肯定应该有边要求到A.

所以ans构造出的图不会满足原图G的要求, 所以cnt - 1是满足条件最小边数.


2. 有环

结论: 假设这个有环图G有cnt个节点, 那么需要cnt条边

证明:

(1) cnt 条边足够

直接把这cnt个节点组成一个大的环, 环中的节点两两可以相 互到达, 肯定满足条件, 所以cnt条边足够.

(2) cnt 条边是最小的

首先对于任何一个 ans (ans < cnt-1)是不可能的, 因为图都不联通了, 理由和无环的情况一样.

然后假设 ans (ans = cnt-1)是一个更优解.

在保证联通的情况下, 用ans个边和cnt个点来构造一个图, 只能构造出一个树, 注意到边是有向的, 所以构造出来的树不可能有环.

而原图G是有环的, 即存在两个节点a, b, 要求a能到b, b能到a. 

所以这个边有向的树不可能满足a和b的条件, 所以cnt条边是满足条件的最小边数.



下面是代码

#include <iostream>
#include <string.h>
#include <vector>
#include <queue>
using namespace std;

#define FOR(i, j, k) for(int i=(j);i<=(k);i++)
#define REP(i, n) for(int i=0;i<(n);i++)
#define mst(x, y) memset(x, y, sizeof(x));

int n, m;
int fa[100009];
int find_(int u){return u==fa[u]?u:fa[u]=find_(fa[u]);}
void merge_(int u, int v){fa[find_(u)] = find_(v);}
vector <int> edge[100008];
vector <int> grp[100009];
int cnt[100008], in[100009];

queue <int> q;
int solve(int id){
    while(!q.empty()) q.pop();
    REP(i, grp[id].size()) if(in[grp[id][i]] == 0) q.push(grp[id][i]);
    int cntt = 0;
    while(!q.empty()){
        cntt ++;
        int u = q.front(); q.pop();
        REP(i, edge[u].size()){
            int v = edge[u][i];
            if(--in[v] == 0) q.push(v);
        }
    }
    return cntt == cnt[id]? cnt[id]-1 : cnt[id];
}

int main(){
    cin>>n>>m;
    FOR(i, 1, n) fa[i] = i;
    mst(in, 0);
    REP(i, m){
        int u, v;
        cin>>u>>v;
        edge[u].push_back(v);
        merge_(u, v);
        in[v] ++;
    }
    mst(cnt, 0);
    FOR(i, 1, n) grp[find_(i)].push_back(i), cnt[find_(i)] ++;
    int ans = 0;
    FOR(i, 1, n) if(grp[i].size()) ans += solve(i);
    cout<<ans<<endl;
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值