hihocoder #1343:题目

解释:
一个学习小组,一共有N个学员,一个主管。每个学员都有自己的导师(一个或者多个),导师可以是其他学员也可以是主管。
每周学员都要把自己的学习报告和收到的报告提交给自己的导师,这个团队设计很合理,没有回环(投递出去的报告不会回到自己手中),并且所有的报告最终都会投递到主管那里。
但这个团队中有的学员会因为其他某个学员不工作而导致报告无法提交到主管手中,我们称这种学员为不可靠的。而不受某个学员不工作而影响到报告提交的就是可靠学员。
问题就是找出可靠学员的数量。

输入:
第一行数字是N,学员总数。接下来每行对应1到N学员的导师数量和编号,例如第二行输入(1 0),代表学员1的导师有1个,并且就是主管(0代表主管);第四行输入(2 1 2),代表学员3的导师有两个,分别是学员1和2。

输出:
可靠学员的数量

题意:一个有向无环图,最上游的点只有一个。若删掉一个点,则某些后续点无法与最上游的点连通,则这些后续点为unstable的。要找出所有unstable的点,然后输出剩下的stable点的数量。

解法一:BFS拓扑

对于每个顶点v,都遍历其后续顶点,找到所有的unstable的点。具体方法如下,采用染色的方法:
对于某个顶点v,采用拓扑排序的方法遍历其后续顶点,并依次染色。后续的顶点进入队列的条件是,其所有的父顶点都已经被染成顶点v的颜色。因为会出现这样的情况,当删掉1时,虽然4的入边有2条,但也是unstable的。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;struct Member
{int color = 0;vector<int> s, p;//son,parent
}mbs[100001];bool is[100001];//unstable
int N;bool all_colored(int v, int color)
{bool res = true;for(int i = 0; res && i < mbs[v].p.size(); i++)res &= (mbs[mbs[v].p[i]].color == color);return res;
}void topo(int v)
{if(mbs[v].color != 0)return;int color = v;mbs[v].color = v;queue<int> que;que.push(v);while(!que.empty()){int u = que.front();que.pop();for(int i = 0; i < mbs[u].s.size(); i++){int s = mbs[u].s[i];if(all_colored(s, color)){mbs[s].color = color;que.push(s);is[s] = true;}}}}int main()
{cin >> N;for(int i = 1; i <= N; i++){int K;cin >> K;for(int j = 0; j < K; j++){int p;cin >> p;mbs[p].s.push_back(i);mbs[i].p.push_back(p);}}memset(is, 0, sizeof(is));for(int i = 1; i <= N; i++)topo(i);int res = 0;for(int i = 1; i <= N; i++)res += is[i];cout << N - res << endl;return 0;
}

解法二: 记忆化搜索DFS【找每个学员的汇聚点】

思考一下,针对不稳定的学员,他的导师路径如果有多条必定会在某个时刻汇聚到同一个学员那里,而稳定的学员汇聚点肯定是自身。

解释:首先对于直接导师中就有主管的,那么汇聚点就是本身,因为本身就是稳定的。其次对于导师只有一个但不是主管的,迭代去找最靠近主管的汇聚点。最后对于有多个导师的情况,就需要分别迭代去找其导师的汇聚点,如果其某两个导师的汇聚点不同,那么他是稳定的,他的汇聚点是自己。如果都一样,那么汇聚点就是其导师们的汇聚点。
如样例输入中:
1、2的导师都是0,所以汇聚点是自己1与2。
3的导师有两个1和2,他们的汇聚点分别是1,2,不同,那么3的汇聚点是3。
4的导师是3,3的汇聚点是3,那么4的汇聚点也是3。
5的导师4和3,汇聚点都是3,所以5的汇聚点也是3。

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>using namespace std;
vector<vector<int>> members;
map<int, int>stableMap;//map存储某个学员的汇聚点int stableNum(int num)
{if (stableMap.find(num) != stableMap.end()) {return stableMap[num];}vector<int> mentors = members[num-1];if (find(mentors.begin(), mentors.end(), 0) != mentors.end()) {stableMap.insert(make_pair(num, num));return num;}else if (mentors.size() == 1){int stable = stableNum(mentors[0]);stableMap.insert(make_pair(mentors[0], stable));return stable;}else{int stable = stableNum(mentors[0]);stableMap.insert(make_pair(mentors[0], stable));for (int i = 1; i < mentors.size(); i++) {int temp = stableNum(mentors[i]);stableMap.insert(make_pair(mentors[i], temp));if (stable != temp) {stableMap.insert(make_pair(num, num));return num;}}stableMap.insert(make_pair(num, stable));return stable;}
}void numOfStableM()
{int sum = 0;for (int i = 0; i < members.size(); i++) {vector<int> mentors = members[i];if (find(mentors.begin(), mentors.end(), 0) != mentors.end()) {sum++;}else if (mentors.size() > 1){if (stableNum(i+1) == i+1) {sum++;}}}cout<<sum<<endl;
}int main() {int N;scanf("%d",&N);while (N--) {int K;scanf("%d",&K);vector<int> mentors;while (K--) {int mentor;scanf("%d",&mentor);mentors.push_back(mentor);}members.push_back(mentors);}numOfStableM();return 0;
}

解法三:支配树【必经节点,LCA】

这里用编号0来表示Master。此时,“不稳定成员”的定义就是:如果存在某一个编号不为0的点,使得从点0到该点的所有路径中都必须经过这个点,那么该点代表的成员就是“不稳定成员”。上图中,从点0到点4的所有路径必经过点3,因此成员4是不稳定成员。
支配树:
    将上面“不稳定成员”的定义加以拓展,去掉“非0点”的限制,可以得到“支配点”的定义,即:对于某一个目标点,如果存在一个点,使得图中从起点到目标点所有路径都必须经过该点,那么该点就是目标点的“支配点”。上图中:点1、2、3、4的支配点分别为:0、0、0、0和3。显然,如果从起点出发可以到达图中的所有点,那么起点就是图中所有点的“支配点”。
    一个点的“支配点”不一定只有一个。例如:如果对于某个点,从起点到该点的路径只有1条,那么该路径上的所有点都是该点的支配点。对于有多个支配点的情况,我们可以找到一个支配点,它距离目标点的最近,这个点我们成为“直接支配点”。对于给定的图,一个点可能用有多个支配点,但它的直接支配点一定是唯一的。此时,出去起点外,所有的点都有自己唯一的“直接支配点”。
    而“支配树”是这样的一种多叉树:图中的点对应于树的节点,而每一个节点的父节点则是它的直接支配点。上文中的图构成的支配树如下:

显然,完成树的构建后,每个点的父节点就是它的直接支配点,而这个点的所有祖先节点都是它的支配点。此时,根据题意,我们要找的“稳定成员”就是直接支配点是0号点(Master)的成员,也就是支配树中根节点的孩子。
·建树:
    为了建立支配树,就必须知道每个点的直接支配点。考虑原图中每个点的“前驱点”,本题中即考虑每个成员的mentor。如果某个成员只有一个mentor,那么显然从Master到该成员的路径一定都会经过他的mentor,因此mentor就是该成员的直接支配点;对于抽象的图而言,如果某一个点只有一个前驱点,那么该前驱点就是当前点的直接支配点;如果某个成员有多个mentor,那么对于某一个mentor而言,从Master到该成员就未必会经过它,所以,当某个成员拥有多个mentor时,他的mentor都不是他的直接支配点;对于抽象的图而言,如果一个点有多个前驱,那么这些前驱点都不是它的直接支配点,我们需要考虑这些前驱节点的支配点,当这些前驱节点拥有共同的一个支配点时,说明从起点到这些前驱点的所有路径必会经过这个共有的支配点,也就是说,从起点到目标点的所有路径都会经过这个共有的支配点,因此这个共有的支配点就是目标点的直接支配点。这个结论对于只有一个前驱节点的情况也使用
    根据支配树的定义,多个节点共有的支配点是明确的,就是他们的公共祖先,而我们要找的则是最近公共祖先。这个结论对于只有一个前驱节点的情况也使用,因为如果只有一个点,那么它的最近公共祖先就是它自己。
    于是,建立支配树的过程就是:首先将起点加入到树中,作为整个支配树的根,然后对于每一个节点,找到其所有前驱节点在支配树中的最近公共祖先,这个祖先节点就是当前节点的父节点。
·拓扑排序:
    上面的建树过程有一个条件必须要保证,即某个点要加入到树中时,必须确保它的所有前驱点已经在树中,这样才可以找到这些点的最近公共祖先。因此,节点添加入树中的顺序是很重要的。我们可以通过拓扑排序找到合适的顺序,拓扑排序的结果就是节点加入树的顺序。这样就保证了前驱节点一定先于当前节点加入到树中。
·最近公共祖先:
    建树的过程设计到了求多个节点的最近公共祖先。我们可以采用一种复杂度为O(lgn)的算法来求解它。考虑每个节点在树中的高度,将高度小的节点沿着父节点指针向上移动,在所有节点的高度相同时再同时沿着父节点指针向上移动,当所有的节点都到达同一个节点时,这个终点就是这些节点的最近公共祖先。

以上是本题的解答思路,完成建立支配树后,统计一下根节点有多少个孩子,就是本题的答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;  const int maxn = 100005;
struct Edge
{  int to,next;
}edge[maxn*10];
int n,cnt,ans,head[maxn],deg[maxn];
int dep[maxn],parent[maxn],tmp[15]; //tmp[i]表示第i个直接前驱回溯到的节点
vector<int> g[maxn];  void addedge(int u,int v)
{  edge[cnt].to = v;  edge[cnt].next = head[u];  head[u] = cnt++;
}  int LCA(int u)
{  int min_dep = -1;  for(int i = 0; i < g[u].size(); i++)  {  int v = g[u][i];  tmp[i] = v;  if(min_dep == -1 || min_dep > dep[v])  min_dep = dep[v];  }  for(int i = 0; i < g[u].size(); i++)  {  while(dep[tmp[i]] > min_dep)  tmp[i] = parent[tmp[i]];  }  while(true)  {  int i;  for(i = 1; i < g[u].size(); i++)  if(tmp[i] != tmp[0])  break;  if(i >= g[u].size()) break;  for(int i = 0; i < g[u].size(); i++)  tmp[i] = parent[tmp[i]];  }  return tmp[0];
}  void bfs()
{  queue<int> q;  for(int i = 0; i <= n; i++)  if(deg[i] == 0)  q.push(i);  while(!q.empty())  {  int u = q.front();  q.pop();  for(int i = head[u]; i != -1; i = edge[i].next)  {  int v = edge[i].to;  deg[v]--;  if(deg[v] == 0)  {  parent[v] = LCA(v);  dep[v] = dep[parent[v]] + 1;  if(parent[v] == 0) ans++;  q.push(v);  }  }  }
}  int main()
{  while(scanf("%d",&n)!=EOF)  {  memset(head,-1,sizeof(head));  memset(deg,0,sizeof(deg));  for(int i = 0; i <= n; i++) g[i].clear();  for(int i = 1; i <= n; i++)  {  int k;  scanf("%d",&k);  for(int j = 1; j <= k; j++)  {  int u;  scanf("%d",&u);  addedge(u,i);  //childg[i].push_back(u); //parentdeg[i]++;  //in degree}  }  ans = 0;  bfs();  printf("%d\n",ans);  }  return 0;
} 

转载于:https://www.cnblogs.com/demian/p/6536799.html

hihocoder 1343 : Stable Members【拓扑排序】相关推荐

  1. hihocoder #1343 : Stable Members(支配树)

    时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 Recently Little Hi joined an algorithm learning group. The gr ...

  2. 题解报告:hihoCoder #1175:拓扑排序·二

    题目链接:https://hihocoder.com/problemset/problem/1175 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho所在学 ...

  3. hihoCoder 1175 拓扑排序

    hihoCoder 1175 拓扑排序 描述 小Hi和小Ho所在学校的校园网被黑客入侵并投放了病毒.这事在校内BBS上立刻引起了大家的讨论,当然小Hi和小Ho也参与到了其中.从大家各自了解的情况中,小 ...

  4. HihoCoder - 1175 拓扑排序·二

    描述 小Hi和小Ho所在学校的校园网被黑客入侵并投放了病毒.这事在校内BBS上立刻引起了大家的讨论,当然小Hi和小Ho也参与到了其中.从大家各自了解的情况中,小Hi和小Ho整理得到了以下的信息: 校园 ...

  5. HihoCoder - 1174 拓扑排序·一

    由于今天上课的老师讲的特别无聊,小Hi和小Ho偷偷地聊了起来. 小Ho:小Hi,你这学期有选什么课么? 小Hi:挺多的,比如XXX1,XXX2还有XXX3.本来想选YYY2的,但是好像没有先选过YYY ...

  6. HDU 3342 Legal or Not(拓扑排序)

    描述 ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmonious that ju ...

  7. 【HDU - 3342】Legal or Not(拓扑排序)

    题干: ACM-DIY is a large QQ group where many excellent acmers get together. It is so harmonious that j ...

  8. 【图论】有向无环图的拓扑排序

    1. 引言 有向无环图(Directed Acyclic Graph, DAG)是有向图的一种,字面意思的理解就是图中没有环.常常被用来表示事件之间的驱动依赖关系,管理任务之间的调度.拓扑排序是对DA ...

  9. C#实现有向无环图(DAG)拓扑排序

    对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在 ...

最新文章

  1. SAP 零售行业相关的博客
  2. 2021年计算机网络期末考试题,2021年计算机网络期末考试试题及答案-20210515145802.doc-原创力文档...
  3. Maven最全教程,还有哪里对maven不解的地方看过来!
  4. 1.使用适配器模式设计一个仿生机器人:要求机器人可以模拟各种动物行为,在机器人中定义了一系列方法,如机器人发声方法talk(),机器人移动方法move()等。如果希望在不改变已有Bird类代码的基础上
  5. C和指针之strcat函数 strchr函数 strcmp函数 strcpy函数 strnchr函数 strstr函数实现
  6. HTML+CSS+JS实现 ❤️echarts省市区地图城市选择❤️
  7. 用java画爱心图_C++和Java命令行绘制心形图案
  8. linux 提高代码质量的工具
  9. python爬取热门新闻每日排行_用python查看百度搜索中今日热点事件排行榜
  10. Django Form
  11. 《编写可维护的JavaScript》——2.2 多行注释
  12. 简单图像灰度化处理python代码
  13. 带本科生做毕设是什么样的体验,看看学生是怎么评价我的
  14. 2020届校园招聘360笔试题
  15. 1003【顺序结构】A+B 问题
  16. Java NIO Selector , SelectionKey , SocketChannel , ServerSocketChannel
  17. Apache Griffin+Flink+Kafka实现流式数据质量监控实战
  18. c 语言获取系统时间并打印机,C# 获取打印机当前状态的方法
  19. Comparable+Comparator+Cloneable接口
  20. CentOS8 源码编译安装 lamp环境 (apache24 + php7.3 + mysql57)

热门文章

  1. Mysql(11)——group by的用法
  2. git reset git stash
  3. linux man命令_CentOS Linux中的man命令
  4. LC.exe”已退出,代码为 -1
  5. 双向链表简单实现及图示
  6. 【Linux】库文件
  7. (学习c++primer5th的重要)c++ primer5th类指针版本hasptr (网上源代码错误) 定义错误
  8. C++ const成员和引用成员
  9. C++顺序容器之deque初探
  10. c++一个问题:while(!cin) 的解释