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); //child g[i].push_back(u); //parent deg[i]++; //in degree } } ans = 0; bfs(); printf("%d\n",ans); } return 0; }