牛奶工厂
牛奶工厂
牛奶生意正红红火火!
农夫约翰的牛奶加工厂内有 个加工站,编号为 ,以及 条通道,每条连接某两个加工站。(通道建设很昂贵,所以约翰选择使用了最小数量的通道,使得从每个加工站出发都可以到达所有其他加工站)。
为了创新和提升效率,约翰在每条通道上安装了传送带。
不幸的是,当他意识到传送带是单向的已经太晚了,现在每条通道只能沿着一个方向通行了!
所以现在的情况不再是从每个加工站出发都能够到达其他加工站了。
然而,约翰认为事情可能还不算完全失败,只要至少还存在一个加工站 满足从其他每个加工站出发都可以到达加工站 。
注意从其他任意一个加工站 前往加工站 可能会经过 和 之间的一些中间站点。
请帮助约翰求出是否存在这样的加工站 。
输入格式
输入的第一行包含一个整数 ,为加工站的数量。
以下 行每行包含两个空格分隔的整数 和 ,满足 以及 。
这表示有一条从加工站 向加工站 移动的传送带,仅允许沿从 到 的方向移动。
输出格式
如果存在加工站 满足可以从任意其他加工站出发都可以到达加工站 ,输出最小的满足条件的 。
否则,输出 。
数据范围
输入样例:
1 3 2 1 2 3 3 2
输出样例:
2
解题思路
题目大意就是给定一颗有向树,问是否存在一个结点,使得树中的其他所有结点都可以走到这个结点。
可以用的传递闭包算法,来判断两个点是否连通,时间复杂度为。
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,如果发现某个结点可以走到其他所有的结点,那么这个结点就是要找的。时间复杂度为。
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 }
下面介绍一种的解法,需要进行证明。
首先如果有解的话,那么解必然是唯一的。假设存在一个点,其他所有的点都可以走到。同时存在一个不同于点的点,同样满足其他所有的点都可以走到。由于这是一颗树,因此任意两个点间的路径是唯一的,由于任何一个点可以走到,所以对于而言,所有边的方向都是往走的方向。又因为任何一个点可以走到,所以可以走到,所以到的边的方向都是往走的方向。因为间的路径是唯一的,因此可以发现这条路径上的边应该具备两个方向。由于这是一颗有向树,即边的方向只有一个,这样就矛盾了。
因此如果有解的话,那么解必然是唯一的。
如果存在一个解的话,即存在一个点,所有的点都可以到达这个点,那么我们让这个点作为树的根节点,可以发现除了根节点外,所有点都会往上指,即存在一个父节点。又因为在一颗树中,每个点的父节点是唯一的,而且除了根节点外,每个点都会有一个父节点,因此除了根节点外,其余的点的出度一定不为,根节点的出度一定为(因为除了根节点外,所有点都有父节点)。
因此我们得到有解的一个必要条件,出度为的点有且只有一个。
我们再证明充分性。
我们设出度为的点为根节点。反证法,假设出度为的点有且只有一个会推出无解,即存在一个点不可以走到根节点。
因此出度为的点不止一个,矛盾了。所以出度为的点有且只有一个可以推出有解。
因此有解与出度为的点有且只有一个是一个充分必要条件。
因此我们只需要记录每一个点的出度,判断是否只有一个点的出度为即可。
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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16099840.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效