TZOJ3647: Hawk-and-Chicken(Tarjan连通分支、反向建图、缩点)

TZOJ3647: Hawk-and-Chicken

题目传送门

描述

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.

输入

There are several test cases. First is an 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.

输出

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.

样例输入
2
4 3
3 2
2 0
2 1

3 3
1 0
2 1
0 2
样例输出
Case 1: 2
0 1
Case 2: 2
0 1 2
解题思路

题目说孩子们可以彼此给出手帕进行投票选举胜出者,胜出者可以有多个,手帕具有传递性、唯一性(每个人从同一个人得到的手帕只能是一个)。说白了就是给出了一些顶点和有向边点是散的,而求的结果可以是多个点的集体,那么可以想到缩点把它们变成一个连通分支,所以要用到Tarjan算法。之后重新将每个连通分支反向建图(便于计算结果)同时记录入度,枚举入度为0的每个分支(也就是得票人),dfs往上找给其投票的分支,加上对应成员数求最大值,然后将符合条件的分支内的成员输出即可。
样例一
样例一如上图,其中0,1,2,3都是一个独立分支,0和1的分支入度是0,反向建图后dfs枚举的就是0→2→3和1→2→3,结果都是2,所以0和1都是胜者。
样例二
样例二如上图,这里成环了所以只有1个分支012,它的入度是0直接求和就是结果了,结果是2,所以0,1,2都是胜者。

代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define IOS ios::sync_with_stdio(0), cin.tie(0)
const ll N = 1e3 * 5 + 5;
ll n, m, dfn[N], low[N], ti, scc[N], scc_cnt, in[N], sum, vis[N];
stack<ll> stk;
vector<ll> g[N], sc[N], gg[N];
unordered_map<ll, ll> st;
void Tarjan(ll u, ll fa) {
    dfn[u] = low[u] = ++ ti;
    stk.push(u);
    for(auto v : g[u]) {
        if(!dfn[v]) {
            Tarjan(v, u);
            low[u] = min(low[u], low[v]);
        } else if(!scc[v]) low[u] = min(low[u], dfn[v]);
    }
    if(low[u] == dfn[u]) {
        ++ scc_cnt;
        while (1) {
            ll id = stk.top(); stk.pop();
            scc[id] = scc_cnt;//标记当前点id在哪个连通分支,缩点
            sc[scc_cnt].push_back(id);//存储当前连通分支内的成员
            if(id == u) break;
        }
    }
}
void dfs(ll u) {
    vis[u] = 1;
    sum += sc[u].size();//由得票人前往各个投票人所在连通分支加上其成员数,即为得票数
    for(auto v : gg[u]) {
        if(vis[v]) continue;
        dfs(v);
    }
}
void init() {
    memset(dfn, 0, sizeof dfn);
    memset(low, 0, sizeof low);
    memset(scc, 0, sizeof scc);
    memset(in, 0, sizeof in);
    memset(vis, 0, sizeof vis);
    for (int i = 0; i < N; ++i) {
        g[i].clear(); gg[i].clear();stk = stack<ll>(); sc[i].clear();st.clear();
    }
    n = m = ti = scc_cnt = sum = 0;
}
ll cas;
void solve() {
    init();
    cin >> n >> m;
    for (int i = 0; i < m; ++i) {
        ll u, v; cin >> u >> v;
        ++ u; ++ v;
        g[u].push_back(v);//单向,u给v投票
    }
    for (int i = 1; i <= n; ++i) {
        if(!dfn[i]) Tarjan(i, i);
    }
    for (int i = 1; i <= n; ++i) {
        for(auto v : g[i]) {
            if(scc[i] != scc[v]) {
                gg[scc[v]].push_back (scc[i]);//得票人反向建边
                ++ in[scc[i]];
            }
        }
    }
    ll ans = 0;
    for (int i = 1; i <= scc_cnt; ++i) {
        if(!in[i]) {//选取入度为0的连通分支,这样得票数才完整
            memset(vis, 0, sizeof vis);
            sum = 0;
            dfs(i);
            st[i] = sum - 1;//根据题意减去自己
            ans = max(ans, st[i]);//得票数最多的连通分支
        }
    }
    cout << "Case " << ++ cas << ": ";
    cout << ans << endl;
    ll f = 0;
    for (int i = 1; i <= n; ++i) {
        if(st[scc[i]] == ans) {//得票数最多的连通分支内的成员都是赢家
            if(f) cout << " "; f = 1;
            cout << i - 1;
        }
    }
    cout << endl;
}

int main( ){
    IOS;
    ll t; cin >> t;
//    ll t = 1;
    while (t--) solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值