2016微软探星 | Stable Members

继续解析微软探星笔试题,本题同样来自2016微软探星夏令营在线技术笔试,主要涉及的知识是图论。
这道题解题的过程就没有前面两道那么顺利了,经过两次TLE(运行时间超过限制),才最终通过。下面我会将超时两次解法也列出来,大家也可以从中汲取经验,不再犯同样错误。


2641798-0eb2dd7c2652957c.JPG

题目:
时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述
Recently Little Hi joined an algorithm learning group. The group consists of one algorithm master and Nmembers. The members are numbered from 1 to N. Each member has one or more other members as his mentors. Some members' mentor is the master himself.
Every week each member sends a report of his own learning progress and the reports collected from his pupils (if there is any) to his mentors. The group is so well designed that there is no loop in the reporting chain so no one receives his own report from his pupil. And finally the master gets every one's report (maybe more than once).
Little Hi notices that for some members their reporting routes to the master can be easily cut off by a single member's (other than the master and himself) absence from the reporting duty. They are called unstable members while the others are stable members. Given the reporting network of the group, can you find out how many members are stable?
Assume there are 4 members in the group. Member 1 and 2 both have the master as their only mentor. Member 3 has 2 mentors: member 1 and member 2. Member 4 has 1 mentor: member 3. Then member 4 is the only unstable member in the group because if member 3 is absent his learning report will be unable to be sent to the master.
输入
The first line contains an integer N, the number of members.
The i-th line of the following N lines describe the mentors of the i-th member. The first integer is Ki, the number of mentors of the i-th member. Then follows Ki integers A1... AN, which are his mentors' numbers. Number 0 indicates that the master is one of his mentor.
For 40% of the data, 1 ≤ N≤ 1000.
For 100% of the data, 1 ≤ N≤ 100000.
For 100% of the data, 1 ≤ Ki ≤ 10, Ki< N, 0 ≤ Ai≤ N.
输出
Output the number of stable members.

样例输入

5
1 0
1 0
2 1 2
1 3
2 4 3

样例输出

3


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

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

输出:
可靠学员的数量。


分析:
1、这是一道图论的题目,首先需要将图存起来。根据输入N的大小和关系分布,我这里用全局的vector<vector<int>> members来存这张图。
2、下面就是如何找出所有稳定学员了。例如样例输入的图:

2641798-ace20c8c54bb8cc6.png
样例输入

我们可以看出1、2肯定是稳定的,因为他的导师中有主管,他会将报告直接提交给主管0,不受其他学员的影响。
3也是稳定的,因为只有1、2同时不工作,报告才没发提交到主管0那里。但其中某一个学员不工作,他的报告还是可以通过另一个学员提交到主管手上。
4是不稳定,因为一旦3不工作,他的报告就没办法提交了。
5也是不稳定的,虽然4不工作,报告可以通过3提交出去,但是如果3不工作,提交给4,4也没办法继续提交。
所有样例输入的输出就是3个稳定学员。

通过对上面样例的分析,我们可以得出下面三种情况:
a. 导师中有主管的学员必然是稳定的;
b. 导师只有一个,并且不是主管,那么必然是不稳定学员;
c. 导师有多个,并且不包含主管,可能是稳定也可能是不稳定的。

现在我们只需要解决第三种情况下的学员就可以完成这道题目了。那么如何解决第三种情况下的问题?
最直接的办法就是尝试掐断某个学员,然后看是否报告就没办法提交到主管那里。如果是,那么当前学员就是不稳定的。根据这个思想就得到我第一个解法。代码如下:

vector<vector<int>> members;
int blockNum = 0;

bool canReachMaster(int num)
{
    vector<int> mentors = members[num-1];
    for (int i = 0; i < mentors.size(); i++) {
        if (mentors[i] == 0) {
            return true;
        }
        else if (mentors[i] != blockNum)
        {
            if (canReachMaster(mentors[i]))
            {
                return true;
            }
        }
    }
    return false;
}

bool isStable(vector<int> mentors)
{
    for (int i = 1; i <= members.size(); i++) {
        blockNum = i;
        bool stable = false;
        for (int j = 0; j < mentors.size(); j++) {
            if (mentors[j] != blockNum) {
                if (canReachMaster(mentors[j])) {
                    stable = true;
                    break;
                }
            }
        }
        if (!stable) {
            return false;
        }
    }
    return true;
}

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 (isStable(mentors)) {
                sum++;
            }
        }
    }
    cout<<sum<<endl;
}

这种解法很明显效率很低,首先最外层有对图中N个点的遍历,针对某些处于第三种情况的点,还有同样的遍历来尝试掐断每个点,虽然有部分优化,但是最坏状况还是会循环N次。而且还会嵌套循环搜索导师路径,因为最终要看是否当前所有导师路径都能走到主管那里。

提交后的结果也是运行超时:

2641798-cb1cfb3d124cb290.png

当然这个解法还有可以优化的地方,也是根据后面第三种解法联想到的。我们可以把每个学员是否是稳定的记录下来,下次调用isStable()时先查记录,没有才进行耗时搜索。

由于第一种解法中,尝试掐断某个点的运算最差情况要运行N次,而且没法优化,因为这个图关系是无序的,不是99的导师一定是小于99的,1的导师可能是99,98的导师又可能是2,所以只能一个个全部遍历。
为了优化这个过程就不能再用第一种方法。其实思考一下,针对不稳定的学员,他的导师路径如果有多条必定会在某个时刻汇聚到同一个学员那里,而稳定的学员汇聚点肯定是自身。根据这个思想我们得到第二种解法,就是找每个学员的汇聚点。代码如下:

int stableNum(int num)
{
    vector<int> mentors = members[num-1];
    if (find(mentors.begin(), mentors.end(), 0) != mentors.end()) {
        return num;
    }
    else if (mentors.size() == 1)
    {
        return stableNum(mentors[0]);
    }
    else
    {
        int stable = stableNum(mentors[0]);
        for (int i = 1; i < mentors.size(); i++) {
            int temp = stableNum(mentors[i]);
            if (stable != temp) {
                return num;
            }
        }
        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;
}

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

同样的,上面这种解法虽然省掉了一个N的循环,但是迭代查询汇聚点还是很耗时。

提交后的结果还是超时:

2641798-7675fe376dd4cade.png

但第二个解法通过分析可以看到做了很多重复的工作,比如找样例中5的汇聚点,会去找4的汇聚点与3的汇聚点。其实这两个点前面已经找过了,并且知道是多少,如果我们存起来,后面直接查询输出那效率高很多。于是就得出了第三种解法。完整代码如下:

//
//  main.cpp
//  Stable Members
//
//  Created by Jiao Liu on 8/8/16.
//  Copyright © 2016 ChangHong. All rights reserved.
//

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>

using namespace std;
vector<vector<int>> members;
map<int, int>stableMap;

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 argc, const char * argv[]) {
    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;
}

这里用map来存储某个学员的汇聚点,map查询和插入操作明显效率高于重复去搜索汇聚点。最后这段代码顺利通过测试:

2641798-9f732a9a2d1cce37.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值