题目链接 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
#include
#include
#include
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 edge[100008];
vector grp[100009];
int cnt[100008], in[100009];
queue 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<