hdu 3639 Hawk-and-Chicken (强连通分量缩点+建返图)

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=3639

Hawk-and-Chicken

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


 

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 total supports 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

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

Sample Output

Case 1: 2

0 1

Case 2: 2

0 1 2

题意:

t 组数据,每组数据有 n 个人,每人都有一种手帕,他们之间的手帕具有传递性,现在给出 m 个行为 x y,表示 x 把手帕给了 y,得到不同种类的手帕最多的人是赢家,如果多个人同时最多,那么他们都是赢家,对于每组数据,问赢家最多能得到的手帕种类是多少,以及有哪些人是赢家

思路:

首先进行强连通缩点,使得所有的强连通分量缩成一个 DAG 图,每个 DAG 图上点的权值代表相应强连通分量缩点前的个数。

在同一个连通分量里的传递出去的手帕数为连通分量内点的个数 -1,假设出度不为 0 的人 x 得到的手帕数最多,那么一定有一个人 y 被 x 给了手帕,必定 y 得到的手帕最多,假设矛盾,故而得到手帕种类最多的一定是出度为 0 的

为方便统计,缩点后建立反向图,然后在反图上对入度为 0 的点,即原 DAG 图上出度为 0 的点做 DFS,将路上的所有点的权值都统计,最后的权值就是包括该点内,所有遇到的点的权值和 -1(排除自己)

This is the code

//vector建图版

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include <iomanip>
#include<list>
#include<queue>
#include<sstream>
#include<stack>
#include<string>
#include<set>
#include<vector>
using namespace std;
#define PI acos(-1.0)
#define EPS 1e-8
#define MOD 1e9+7
#define LL long long
#define ULL unsigned long long     //1844674407370955161
#define INT_INF 0x7f7f7f7f      //2139062143
#define LL_INF 0x7f7f7f7f7f7f7f7f //9187201950435737471
const int dr[]= {0, 0, -1, 1, -1, -1, 1, 1};
const int dc[]= {-1, 1, 0, 0, -1, 1, -1, 1};
const int maxm=30005;
const int maxn=5005;
vector<int> G[maxn];//原图
vector<int> rG[maxn];//返图
bool vis[maxn];//遍历图
int in[maxn];//入度数
int belong[maxn];//缩点后的强连通分量
int res[maxn];//存储结果
int num[maxn];//第i个强连通分量中点的个数
int dfn[maxn],low[maxn];//tarjan使用
stack<int> s;//缩点使用
int dfn_clock;//tarjan的时间记录
int sccNum;//强连通分量(strongly connected components)的个数

void init(int n)//初始化
{
    for(int i=0; i<=n; ++i)
    {
        G[i].clear();
        rG[i].clear();
        dfn[i]=0;
        low[i]=0;
        in[i]=0;
        res[i]=0;
        vis[i]=0;
    }
    dfn_clock=0;
    sccNum=0;
    while(s.size())//注意清空
        s.pop();
}

void tarjan(int u)
{
    dfn[u]=low[u]=++dfn_clock;
    s.push(u);
    vis[u]=true;
    for (int i=0; i<G[u].size(); ++i)
    {
        int v=G[u][i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]&&low[u]>dfn[v])
            low[u]=dfn[v];
    }
    int v;
    if(low[u]==dfn[u])//缩点
    {
        sccNum++;
        num[sccNum]=0;//需要初始化
        while(true)
        {
            v=s.top();
            s.pop();
            vis[v]=false;
            belong[v]=sccNum;
            num[sccNum]++;
            if(v==u)
                break;
        }
    }
}

int dfs(int u)//寻找每个出度点的联通块数量
{
    vis[u]=true;
    int sum=num[u];
    for(int i=0;i<rG[u].size();++i)
    {
        int v=rG[u][i];
        if(!vis[v])
            sum+=dfs(v);
    }
    return sum;
}

void slove(int n)
{
    for(int i=0; i<n; ++i)
        if(!dfn[i])
            tarjan(i);
    for(int u=0; u<n; ++u)
    {
        for(int i=0; i<G[u].size(); ++i)
        {
            int v=G[u][i];
            int x=belong[u];//使用缩点后的图建立反向图
            int y=belong[v];
            if(x==y)
                continue;
            rG[y].push_back(x);
            in[x]++;
        }
    }
    int Sum=-INT_INF;
    for(int i=1; i<=sccNum; ++i)
    {
        if(in[i]==0)//寻找初出度为1.起始点
        {
            memset(vis,false,sizeof(vis));
            res[i]=dfs(i);
            Sum=max(Sum,res[i]);
        }
    }
    printf("%d\n",Sum-1);//减去本身
    bool flag=true;
    for(int i=0; i<n; i++)
    {
        if(res[belong[i]]==Sum)//寻找那些是最多的
        {
            if(flag)
            {
                flag=false;
                printf("%d",i);
            }
            else
                printf(" %d",i);
        }
    }
    printf("\n");
}

int main()
{
    int T;
    scanf("%d",&T);
    for(int t=1; t<=T; ++t)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        init(n);
        for(int i=0; i<m; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            G[u].push_back(v);
        }
        printf("Case %d: ",t);
        slove(n);
    }
}

//l邻接链表版

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#define PI acos(-1.0)
#define E 1e-6
#define MOD 1000000007
#define INF 0x3f3f3f3f
#define N 100001
#define LL long long
using namespace std;
 
struct Edge{
    int to;
    int next;
};
Edge edge[N];//原图
Edge egdeT[N];//反图
int head[N],cnt;//原图
int headT[N],cntT;//反图
bool vis[N];//原图
bool visT[N];//反图
int dfn[N],low[N];
int bolckTime;//时间戳
int scc;//强连通分量个数
int inDegree[N];//入度
int Stack[N],top;
int belong[N];//缩点后的强连通分量
int res[N];//存储结果
int num[N];//第i个强连通分量中点的个数
void addEdge(int x,int y){//原图添边
    edge[cnt].to=y;
    edge[cnt].next=head[x];
    head[x]=cnt++;
}
void addEdgeT(int x,int y){//反图添边
    egdeT[cntT].to=y;
    egdeT[cntT].next=headT[x];
    headT[x]=cntT++;
}
void Tarjan(int x){
    dfn[x]=low[x]=++bolckTime;
    Stack[top++]=x;
    vis[x]=true;
 
    for (int i=head[x];i!=-1;i=edge[i].next){
        int y=edge[i].to;
        if(!dfn[y]){
            Tarjan(y);
            if(low[x]>low[y])
                low[x]=low[y];
        }
        else if(vis[y]&&low[x]>dfn[y])
            low[x]=dfn[y];
    }
 
    int y;
    if(low[x]==dfn[x]){
        scc++;
        do{
            y=Stack[--top];
            vis[y]=false;
            belong[y]=scc;
            num[scc]++;
        }while(y!=x);
    }
 
    return ;
}
int dfs(int x){
    visT[x]=true;
    int sum=num[x];
    for(int i=headT[x];i!=-1;i=egdeT[i].next){
        int y=egdeT[i].to;
        if(!visT[y])
            sum+=dfs(y);
    }
    return sum;
}
 
void solve(int n)
{
    memset(num,0,sizeof(num));
    memset(dfn,0,sizeof(dfn));
    memset(inDegree,0,sizeof(inDegree));
    memset(vis,false,sizeof(vis));
    top=bolckTime=scc=0;
 
    for(int i=0;i<n;i++)
        if(!dfn[i])
            Tarjan(i);
 
 
    for(int x=0;x<n;x++){
        for(int i=head[x];i!=-1;i=edge[i].next){
            int y=edge[i].to;
            if (belong[x]==belong[y])
                continue;
 
            addEdgeT(belong[y],belong[x]);//反向建图
            inDegree[belong[x]]++;
        }
    }
 
 
    int maxx=-INF;
    memset(res,-INF,sizeof(res));
    for(int i=1;i<=scc;i++){
        if(inDegree[i]==0){
            memset(visT,false,sizeof(visT));
            res[i]=dfs(i);
            maxx=max(maxx,res[i]);
        }
    }
    printf("%d\n",maxx-1);
 
    bool flag=true;
    for(int i=0;i<n;i++){
        if(res[belong[i]]==maxx){
            if(flag){
                flag=false;
                printf("%d",i);
            }
            else
                printf(" %d",i);
        }
    }
    printf("\n");
}
 
int main(){
    int t;
    scanf("%d",&t);
 
    int Case=1;
    while (t--){
        int n,m;
        scanf("%d%d",&n,&m);
 
        cnt=cntT=0;
        memset(headT,-1,sizeof(headT));
        memset(head,-1,sizeof(head));
 
        for (int i=0;i<m;i++){
            int x,y;
            scanf("%d%d",&x,&y);
            addEdge(x,y);
        }
        printf("Case %d: ",Case++);
        solve(n);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值