codeforces 1239D-Catowice City(tarjan缩点)

31 篇文章 0 订阅
13 篇文章 2 订阅

codeforces

洛谷

d i f f i c u l t : 2400 difficult:2400 difficult2400


难以想到的 t a r j a n tarjan tarjan 缩点

考虑朴素做法时可以发现:若存在一条 i − > j   ( i ≠ j ) i->j\ (i≠j) i>j (i=j) 的有向边(默认为 i i i 人和 j j j 猫),则可选择的方案有:

  1. i , j i,j ij 同时选人
  2. i , j i,j ij 同时选猫
  3. i i i 选猫, j j j 选人

在该基础上,如果还存在一条 j − > i j->i j>i 的有向边,此时 i 、 j i、j ij 构成强联通分量,则可选方案仅剩:全选人或全选猫。特别的,编号相同的人和猫也能被看作强联通分量,由此进行 t a r j a n tarjan tarjan 缩点。

由于同一强联通分量内只能同时选人或同时选猫,则强联通块数 = 1 =1 =1 时无解。

在其它情况下,由于方案 3 3 3 的限制,考虑让没有出度的强联通分量全选人,其它全选猫。

t a r j a n tarjan tarjan 缩点的特殊性已保证最后缩到点 1 1 1 的即为没有出度的强联通分量。

参考代码

#include <bits/stdc++.h>
#define int long long
#define PII pair<int, int>
using namespace std;

const int N = 1e6 + 10;
vector<int> h[N];
bool vis[N], in[N];
int dfn[N], low[N], suo[N], num[N];
stack<int> st;
int tmp = 0, cnt = 0, x = 0;
vector<PII> p;
vector<int> ans[N];
map<PII, int> mp;

void tarjan(int u) {
    dfn[u] = low[u] = ++x;
    vis[u] = in[u] = true;
    st.push(u);

    for (auto v : h[u]) {
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if (in[v])
            low[u] = min(low[u], dfn[v]);
    }

    if (dfn[u] == low[u]) {
        cnt++;
        do {
            tmp = st.top();
            st.pop();
            in[tmp] = false;
            suo[tmp] = cnt;
            ans[cnt].push_back(tmp);  //缩到cnt中的点集
        } while (u != tmp);
    }
}
int n, m;

void init() {
    for (int i = 1; i <= n; i++) {
        h[i].clear();
        dfn[i] = low[i] = suo[i] = num[i] = in[i] = vis[i] = 0;
        ans[i].clear();
    }
    tmp = 0, cnt = 0, x = 0;
    p.clear(), mp.clear();
}

void solve() {
    cin >> n >> m;
    init();
    while (m--) {
        int u, v;
        cin >> u >> v;
        if (u == v)
            continue;
        p.push_back({u, v});
        h[u].push_back(v);
    }
    for (int i = 1; i <= n; i++) {
        if (!dfn[i])
            tarjan(i);
    }

    if (cnt <= 1) {  //只选人
        cout << "NO" << endl;
        return;
    }

    cout << "YES" << endl;
    cout << ans[1].size() << " " << n - ans[1].size() << endl;
    for (auto j : ans[1])
        cout << j << " ";
    cout << endl;
    for (int i = 2; i <= cnt; i++) {
        for (auto j : ans[i])
            cout << j << " ";
    }
    cout << endl;
}

signed main() {
    ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T = 1;
    cin >> T;
    while (T--)
        solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值