2016微软探星 | Stable Members

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



题目:
时间限制: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、下面就是如何找出所有稳定学员了。例如样例输入的图:

样例输入

我们可以看出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次。而且还会嵌套循环搜索导师路径,因为最终要看是否当前所有导师路径都能走到主管那里。

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

当然这个解法还有可以优化的地方,也是根据后面第三种解法联想到的。我们可以把每个学员是否是稳定的记录下来,下次调用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的循环,但是迭代查询汇聚点还是很耗时。

提交后的结果还是超时:

但第二个解法通过分析可以看到做了很多重复的工作,比如找样例中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查询和插入操作明显效率高于重复去搜索汇聚点。最后这段代码顺利通过测试:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容