hihocoder 1343 : Stable Members【拓扑排序】

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;
} 
时间: 2024-10-14 09:38:42

hihocoder 1343 : Stable Members【拓扑排序】的相关文章

hihoCoder 1175:拓扑排序二

题目链接: http://hihocoder.com/problemset/problem/1175 题目难度:一星级(简单题) 今天闲来无事,决定刷一道水题.结果发现这道水题居然把我卡了将近一个钟头. 最后终于调通了.总结起来,原因只有一个:不够仔细. 思路不用细说了,就是拓扑排序的简单应用.然而,一些不起眼的细节才是让你掉坑里的真正原因. 猜猜哪儿可能出bug? // A simple problem, but you can't be too careful with it. #inclu

hihoCoder #1174:拓扑排序&amp;#183;一

[题目链接]:click here~~ 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描写叙述 因为今天上课的老师讲的特别无聊.小Hi和小Ho偷偷地聊了起来. 小Ho:小Hi.你这学期有选什么课么? 小Hi:挺多的,比方XXX1,XXX2还有XXX3.本来想选YYY2的,可是好像没有先选过YYY1.不能选YYY2. 小Ho:先修课程真是个麻烦的东西呢. 小Hi:没错呢. 好多课程都有先修课程.每次选课之前都得先查查有没有先修.教务发布的先修课程记录都是好多年前的.不但有

hihoCoder#1185 : 连通性&#183;三 tarjan求强联通分量 缩点 dfs/拓扑排序求路径和最大值

题目链接: http://hihocoder.com/problemset/problem/1185# 题意: n个点,每个点有一个权值,m条有向边,从1出发,每走到一个点, 就吃掉这个点的草,当没有可以到达的草场或是能够到达的草场都已经被吃光了之后就要返回到1了.求最多可以吃掉多少草. 思路: 提示里面讲的挺好的 如果草场是一个强连通图,那么我们只要走到任意一点,就可以把其他所有的草场都走一遍,并且可以选择任意一个点作为终点.所以把强联通块缩成一个点 因为一个强连通块会被缩成一个点,那么我们可

题解报告:hihoCoder #1174:拓扑排序&#183;一

题目链接:https://hihocoder.com/problemset/problem/1174 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 由于今天上课的老师讲的特别无聊,小Hi和小Ho偷偷地聊了起来. 小Ho:小Hi,你这学期有选什么课么? 小Hi:挺多的,比如XXX1,XXX2还有XXX3.本来想选YYY2的,但是好像没有先选过YYY1,不能选YYY2. 小Ho:先修课程真是个麻烦的东西呢. 小Hi:没错呢.好多课程都有先修课程,每次选课之前都得先查查

hihocoder 1457(后缀自动机+拓扑排序)

题意 给定若干组由数字构成的字符串,求所有不重复子串的和(把他们看成十进制),答案mod(1e9+7) 题解: 类似后缀数组的做法,把字符串之间用':'连接,这里用':'是因为':'的ascii码恰好是9的下一个 然后建立后缀自动机. 之后把其实只要把其中的所有':'边删去,就可以进行转移了 如果x连向了y,边权是c,那么有转移 dp[y] += dp[x]*10 + c*sz[x] 所以只要拓扑排序一下就好 (写这题wa了好几次,主要是在删边建立新图的过程出了问题) #include <ios

hihoCoder 1174 拓扑排序&#183;一

#1174 : 拓扑排序·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 由于今天上课的老师讲的特别无聊,小Hi和小Ho偷偷地聊了起来. 小Ho:小Hi,你这学期有选什么课么? 小Hi:挺多的,比如XXX1,XXX2还有XXX3.本来想选YYY2的,但是好像没有先选过YYY1,不能选YYY2. 小Ho:先修课程真是个麻烦的东西呢. 小Hi:没错呢.好多课程都有先修课程,每次选课之前都得先查查有没有先修.教务公布的先修课程记录都是好多年前的,不但有重复的信息,好像

hihoCoder#1175拓扑排序应用

时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho所在学校的校园网被黑客入侵并投放了病毒.这事在校内BBS上立刻引起了大家的讨论,当然小Hi和小Ho也参与到了其中.从大家各自了解的情况中,小Hi和小Ho整理得到了以下的信息: 校园网主干是由N个节点(编号1..N)组成,这些节点之间有一些单向的网路连接.若存在一条网路连接(u,v)链接了节点u和节点v,则节点u可以向节点v发送信息,但是节点v不能通过该链接向节点u发送信息. 在刚感染病毒时,校园网立刻切断

HihoCoder 拓扑排序 #1174 &amp;&amp; #1175

#1174 点击打开链接 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 由于今天上课的老师讲的特别无聊,小Hi和小Ho偷偷地聊了起来. 小Ho:小Hi,你这学期有选什么课么? 小Hi:挺多的,比如XXX1,XXX2还有XXX3.本来想选YYY2的,但是好像没有先选过YYY1,不能选YYY2. 小Ho:先修课程真是个麻烦的东西呢. 小Hi:没错呢.好多课程都有先修课程,每次选课之前都得先查查有没有先修.教务公布的先修课程记录都是好多年前的,不但有重复的信息,好像很多

hihoCoder - 1175 - 拓扑排序&#183;二 (拓扑排序的应用)

#1175 : 拓扑排序·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho所在学校的校园网被黑客入侵并投放了病毒.这事在校内BBS上立刻引起了大家的讨论,当然小Hi和小Ho也参与到了其中.从大家各自了解的情况中,小Hi和小Ho整理得到了以下的信息: 校园网主干是由N个节点(编号1..N)组成,这些节点之间有一些单向的网路连接.若存在一条网路连接(u,v)链接了节点u和节点v,则节点u可以向节点v发送信息,但是节点v不能通过该链接向节点u发送信息.