hdoj 3639 Hawk-and-Chicken 【有向图SCC】【缩点后反向建图 求最大传递值】

来源:互联网 发布:mac 从远程拷贝文件夹 编辑:程序博客网 时间:2024/06/05 06:38

Hawk-and-Chicken

Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2353    Accepted Submission(s): 698


Problem Description
Kids in kindergarten enjoy playing a game called Hawk-and-Chicken. But there always exists a big problem: every kid in this game want to play the role of Hawk.
So the teacher came up with an idea: Vote. Every child have some nice handkerchiefs, and if he/she think someone is suitable for the role of Hawk, he/she gives a handkerchief to this kid, which means this kid who is given the handkerchief win the support. Note the support can be transmitted. Kids who get the most supports win in the vote and able to play the role of Hawk.(A note:if A can win
support from B(A != B) A can win only one support from B in any case the number of the supports transmitted from B to A are many. And A can't win the support from himself in any case.
If two or more kids own the same number of support from others, we treat all of them as winner.
Here's a sample: 3 kids A, B and C, A gives a handkerchief to B, B gives a handkerchief to C, so C wins 2 supports and he is choosen to be the Hawk.
 

Input
There are several test cases. First is a integer T(T <= 50), means the number of test cases.
Each test case start with two integer n, m in a line (2 <= n <= 5000, 0 <m <= 30000). n means there are n children(numbered from 0 to n - 1). Each of the following m lines contains two integers A and B(A != B) denoting that the child numbered A give a handkerchief to B.
 

Output
For each test case, the output should first contain one line with "Case x:", here x means the case number start from 1. Followed by one number which is the totalsupports the winner(s) get.
Then follow a line contain all the Hawks' number. The numbers must be listed in increasing order and separated by single spaces.
 

Sample Input
24 33 22 02 13 31 02 10 2
 

Sample Output
Case 1: 20 1Case 2: 20 1 2
 

题意:有N个孩子编号从0到N-1,在玩 ”老鹰捉小鸡“ 游戏。每局游戏只有一个人当老鹰,为了公平,老师采用了传递手帕的方式来确定谁来当老鹰。
规则如下
1,自己不能给自己传递手帕;
2,当A传给B,B又传给C后,可以认为A传给了C手帕;
3,不管有多少个人传给B手帕,当B传递给A时,只认为A被传递了一张手帕;
4,最后被传递手帕最多的人是赢家;
现在已经给出M组传递关系<a,b>,表示a会传递给b一张手帕。
问你  最后的赢家被传递了多少张手帕 ,若有多个赢家,按编号升序输出。

第一次分析:明显和有向图SCC有关的题目,一开始我想的是用Floyd预处理传递闭包,然后SCC缩点统计每个SCC入度,最后枚举所有SCC,选取最大的scc[ i ].size() - 1 + in[ i ]值(1 <= i <= scc_cnt)。最后发现N值过大,没法预处理传递闭包,无语死了,还奉献一次MLE。

第二次分析:对一个编号为u的SCC而言,预处理传递闭包 就是为了 求出 有多少个SCC可以通过边的关系传递到u,反过来说,就是求 u 通过边的关系可以传递到 多少个SCC。这样思路就清晰了。

思路:对原有向图求出SCC,缩点后反向建图。对新图,选取入度为0的SCC,进行一次DFS,统计出该SCC的传递值,一直更新这个值即可。输出部分,枚举所有点,比较这个点所在SCC的传递值 和 上一步求出的答案,相同则输出。


AC代码:注意在DFS求SCC传递值的时候,必须用一个数组来标记一个点是否查询。

给出一组数据

103 31 02 01 2
若没有标记会输出 3, 有标记才输出正确答案2.。当然这是我个人的理解。


#include <cstdio>#include <cstring>#include <queue>#include <stack>#include <vector>#include <algorithm>#define MAXN 5000+10#define MAXM 30000+100using namespace std;struct Edge{    int from, to, next;};Edge edge[MAXM];int head[MAXN], edgenum;int low[MAXN], dfn[MAXN];int dfs_clock;int sccno[MAXN], scc_cnt;vector<int> scc[MAXN];stack<int> S;bool Instack[MAXN];int N, M;void init(){    edgenum = 0;    memset(head, -1, sizeof(head));}void addEdge(int u, int v){    Edge E = {u, v, head[u]};    edge[edgenum] = E;    head[u] = edgenum++;}void getMap(){    int a, b;    while(M--)    {        scanf("%d%d", &a, &b);        addEdge(a, b);    }}void tarjan(int u, int fa){    int v;    low[u] = dfn[u] = ++dfs_clock;    S.push(u);  Instack[u] = true;    for(int i = head[u]; i != -1; i = edge[i].next)    {        v = edge[i].to;        if(!dfn[v])        {            tarjan(v, u);            low[u] = min(low[u], low[v]);        }        else if(Instack[v])            low[u] = min(low[u], dfn[v]);    }    if(low[u] == dfn[u])    {        scc_cnt++;        scc[scc_cnt].clear();        for(;;)        {            v = S.top(); S.pop();            Instack[v] = false;            sccno[v] = scc_cnt;            scc[scc_cnt].push_back(v);            if(v == u) break;        }    }}void find_cut(int l, int r){    dfs_clock = scc_cnt = 0;    memset(low, 0, sizeof(low));    memset(dfn, 0, sizeof(dfn));    memset(sccno, 0, sizeof(sccno));    memset(Instack, false, sizeof(Instack));    for(int i = l; i <= r; i++)        if(!dfn[i]) tarjan(i, -1);}int num[MAXN];//记录SCC里面任意一点 获得的手帕数目int in[MAXN];//记录SCC的入度vector<int> G[MAXN];//存储缩点后新图void suodian()//缩点{    for(int i = 1; i <= scc_cnt; i++) G[i].clear(), in[i] = 0;    for(int i = 0; i < edgenum; i++)    {        int u = sccno[edge[i].from];        int v = sccno[edge[i].to];        if(u != v)//反向建图            G[v].push_back(u), in[u]++;    }}bool vis[MAXN];//标记该点是否查询过 这个数组一定要用int sum;void DFS(int u){    sum += scc[u].size();//WA到死!!! 这里 加错了。。。    vis[u] = true;    for(int i = 0; i < G[u].size(); i++)    {        int v = G[u][i];        if(vis[v]) continue;        DFS(v);    }}int k = 1;void solve(){    int ans = 0;    for(int i = 1; i <= scc_cnt; i++)    {        num[i] = 0;//初始化        if(!in[i])//只有入度为0的SCC里面的点 才有可能得到最多的传递值        {            sum = 0;//DFS过程中把自己也算上了            memset(vis, false, sizeof(vis));            DFS(i);            num[i] = sum - 1;//SCC里面自己不能传递给自己            ans = max(num[i], ans);        }    }    printf("Case %d: %d\n", k++, ans);    int p = 0;    for(int i = 0; i <= N-1; i++)    {        if(num[sccno[i]] == ans)        {            if(p > 0) printf(" ");            printf("%d", i);            p++;        }    }    printf("\n");}int main(){    int t;    scanf("%d", &t);    while(t--)    {        scanf("%d%d", &N, &M);        init();        getMap();        find_cut(0, N-1);        suodian();        solve();    }    return 0;}




0 0
原创粉丝点击