牛奶工厂
作者:互联网
牛奶工厂
牛奶生意正红红火火!
农夫约翰的牛奶加工厂内有 $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