其他分享
首页 > 其他分享> > 牛奶工厂

牛奶工厂

作者:互联网

牛奶工厂

牛奶生意正红红火火!

农夫约翰的牛奶加工厂内有 $N$ 个加工站,编号为 $1 \dots N$,以及 $N−1$ 条通道,每条连接某两个加工站。(通道建设很昂贵,所以约翰选择使用了最小数量的通道,使得从每个加工站出发都可以到达所有其他加工站)。

为了创新和提升效率,约翰在每条通道上安装了传送带。

不幸的是,当他意识到传送带是单向的已经太晚了,现在每条通道只能沿着一个方向通行了!

所以现在的情况不再是从每个加工站出发都能够到达其他加工站了。

然而,约翰认为事情可能还不算完全失败,只要至少还存在一个加工站 $i$ 满足从其他每个加工站出发都可以到达加工站 $i$。

注意从其他任意一个加工站 $j$ 前往加工站 $i$ 可能会经过 $i$ 和 $j$ 之间的一些中间站点。

请帮助约翰求出是否存在这样的加工站 $i$。 

输入格式

输入的第一行包含一个整数 $N$,为加工站的数量。

以下 $N−1$ 行每行包含两个空格分隔的整数 $a_{i}$ 和 $b_{i}$,满足 $1 \leq a_{i},b_{i} \leq N$ 以及 $a_{i} \ne b_{i}$。

这表示有一条从加工站 $a_{i}$ 向加工站 $b_{i}$ 移动的传送带,仅允许沿从 $a_{i}$ 到 $b_{i}$ 的方向移动。

输出格式

如果存在加工站 $i$ 满足可以从任意其他加工站出发都可以到达加工站 $i$,输出最小的满足条件的 $i$。

否则,输出 $−1$。

数据范围

$1 \leq N \leq 100$

输入样例:

1 3
2 1 2
3 3 2

输出样例:

2

 

解题思路

  题目大意就是给定一颗有向树,问是否存在一个结点,使得树中的其他所有结点都可以走到这个结点。

  可以用$Floyd$的传递闭包算法,来判断两个点是否连通,时间复杂度为$O \left( n^{3} \right)$。

  AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 110;
 6 
 7 bool graph[N][N];
 8 
 9 int main() {
10     int n;
11     scanf("%d", &n);
12     for (int i = 1; i <= n; i++) {
13         graph[i][i] = true;
14     }
15     
16     for (int i = 0; i < n - 1; i++) {
17         int v, w;
18         scanf("%d %d", &v, &w);
19         graph[v][w] = true;
20     }
21     
22     for (int k = 1; k <= n; k++) {
23         for (int i = 1; i <= n; i++) {
24             for (int j = 1; j <= n; j++) {
25                 // 如果graph[i][k] == true意味着可以从i到达k,同理graph[k][j],如果都为true,意味着可以从i经过k到达j
26                 graph[i][j] |= graph[i][k] & graph[k][j];
27             }
28         }
29     }
30     
31     for (int j = 1; j <= n; j++) {
32         int cnt = 0;
33         for (int i = 1; i <= n; i++) {
34             if (graph[i][j]) cnt++;
35         }
36         if (cnt == n) {
37             printf("%d", j);
38             return 0;
39         }
40     }
41     
42     printf("-1");
43     
44     return 0;
45 }

  也可以用dfs。是否存在一个结点,使得树中的其他所有结点都可以走到这个结点,等价于是否存在一个结点,这个结点可以走到其他所有的结点。因此可以对每一个结点都进行一次dfs,如果发现某个结点可以走到其他所有的结点,那么这个结点就是要找的。时间复杂度为$O \left( n^{2} \right)$。

  AC代码如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 110;
 7 
 8 int head[N], e[N], ne[N], idx;
 9 
10 void add(int v, int w) {
11     e[idx] = w, ne[idx] = head[v], head[v] = idx++;
12 }
13 
14 // 返回以src为根的子树所包含的结点个数
15 int dfs(int src) {
16     int cnt = 1;
17     for (int i = head[src]; i != -1; i = ne[i]) {
18         cnt += dfs(e[i]);
19     }
20     
21     return cnt;
22 }
23 
24 int main() {
25     memset(head, -1, sizeof(head));
26     
27     int n;
28     scanf("%d", &n);
29     for (int i = 1; i <= n - 1; i++) {
30         int v, w;
31         scanf("%d %d", &v, &w);
32         add(w, v);
33     }
34     
35     int ret = -1;
36     for (int i = 1; i <= n; i++) {
37         if (dfs(i) == n) {
38             ret = i;
39             break;
40         }
41     }
42     
43     printf("%d", ret);
44     
45     return 0;
46 }

  下面介绍一种$O \left( n \right)$的解法,需要进行证明。

  首先如果有解的话,那么解必然是唯一的。假设存在一个点$A$,其他所有的点都可以走到$A$。同时存在一个不同于点$A$的点$B$,同样满足其他所有的点都可以走到$B$。由于这是一颗树,因此任意两个点间的路径是唯一的,由于任何一个点可以走到$A$,所以对于$B$而言,所有边的方向都是往$A$走的方向。又因为任何一个点可以走到$B$,所以$A$可以走到$B$,所以$A$到$B$的边的方向都是往$B$走的方向。因为$A$间$B$的路径是唯一的,因此可以发现$A, B$这条路径上的边应该具备两个方向。由于这是一颗有向树,即边的方向只有一个,这样就矛盾了。

  因此如果有解的话,那么解必然是唯一的。

  如果存在一个解的话,即存在一个点,所有的点都可以到达这个点,那么我们让这个点作为树的根节点,可以发现除了根节点外,所有点都会往上指,即存在一个父节点。又因为在一颗树中,每个点的父节点是唯一的,而且除了根节点外,每个点都会有一个父节点,因此除了根节点外,其余的点的出度一定不为$0$,根节点的出度一定为$0$(因为除了根节点外,所有点都有父节点)。

  因此我们得到有解的一个必要条件,出度为$0$的点有且只有一个。

  我们再证明充分性。

  我们设出度为$0$的点为根节点。反证法,假设出度为$0$的点有且只有一个会推出无解,即存在一个点不可以走到根节点。

  因此出度为$0$的点不止一个,矛盾了。所以出度为$0$的点有且只有一个可以推出有解。

  因此有解与出度为$0$的点有且只有一个是一个充分必要条件。

  因此我们只需要记录每一个点的出度,判断是否只有一个点的出度为$0$即可。

  AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 110;
 6 
 7 int deg[N];
 8 
 9 int main() {
10     int n;
11     scanf("%d", &n);
12     for (int i = 0; i < n - 1; i++) {
13         int v, w;
14         scanf("%d %d", &v, &w);
15         deg[v]++;
16     }
17     
18     int cnt = 0, ret;
19     for (int i = 1; i <= n; i++) {
20         if (deg[i] == 0) {
21             cnt++;
22             ret = i;
23         }
24     }
25     printf("%d", cnt == 1 ? ret : -1);
26     
27     return 0;
28 }

 

参考资料

  AcWing 1471. 牛奶工厂(寒假每日一题2022):https://www.acwing.com/video/3706/

标签:结点,牛奶,加工,int,出度,工厂,include,节点
来源: https://www.cnblogs.com/onlyblues/p/16099840.html