Tarjan入门②-缩点(洛谷P3387)

缩点其实算Tarjan求强连通分量的一个应用吧.

由于一个强连通分量内部可以互通.所以可以将其缩成一个点.把图变成DAG.然后就可以跑一跑dp啥的了.

模板题目1:洛谷P3387
题目大意:

给你一个有向连通图.每个点有点权.问你找出一条路径使得点权值最大.每个点和每个边能够经过多次.但是只算一次点权.

题目思路:

缩点,然后DAG上跑最长路即可.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 4;
vector<int> e[maxn];
int dfn[maxn] , low[maxn] , vis[maxn] , a[maxn] , val[maxn];
stack<int> s;
int cnt = 0 , tot = 0; // tot 用来给强连通分量标号,用来缩点建边
int id[maxn];
vector<int> new_e[maxn];
void tarjan (int u)
{
    dfn[u] = low[u] = ++cnt;
    vis[u] = 1;
    s.push(u);
    for (auto v : e[u]){
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min (low[u] , low[v]);
        }
        else if (vis[v]){
            low[u] = min (low[u] , dfn[v]);
        }
    }
    if (dfn[u] == low[u]){
        ++tot;
        while (1){
            int now = s.top();
            s.pop();
            val[tot] += a[now];
            id[now] = tot;
            vis[now] = 0;
            if (now == u) break;
        }
    }
}
int dp[maxn];
int dfs (int u)
{
    if (~dp[u]) return dp[u];
    int ans = 0;
    for (auto v : new_e[u]){
        ans = max (ans , dfs(v));
    }
    return dp[u] = ans + val[u];
}
int main()
{
    ios::sync_with_stdio(false);
    int n , m;cin >> n >> m;
    for (int i = 1 ; i <= n ; i++){
        cin >> a[i];
    }
    for (int i = 1 ; i <= m ; i++){
        int x , y; cin >> x >> y;
        e[x].push_back(y);
    }
    for (int i = 1 ; i <= n ; i++){
        if (!dfn[i]) tarjan(i);
    }
    for (int i = 1 ; i <= n ; i++){
        for (auto v : e[i]){
            if (id[i] != id[v]){
                new_e[id[i]].push_back(id[v]);
            }
        }
    }
   // cout << tot << endl;
    memset(dp , -1 , sizeof dp);
    int ans = 0;
    for (int i = 1 ; i <= tot ; i++){
        dfs(i);
        ans = max (ans , dp[i]);
    }
    cout << ans << endl;
    return 0;
}
模板题目2:洛谷P2341-受欢迎的牛
题目大意:

给你一张n点m边的有向连通图.问你有多少个点是其他所有点能够达到的.

n , m ≤ 1 e 5 n,m \leq 1e5 n,m1e5

题目思路:

缩点的一个应用。稍微需要一些推理。

考虑同一个强连通分量之间可以互通。那么对于有若干个强连通分量的图:当且仅当图中只有一个出度为0的强连通分量时,存在答案。答案即为这个分量的大小

解释:假设一个强连通分量S有出度。设到达的分量为T。那么T是无法到达S的。

因为如果T能够达到S的话,T,S就能够互达了。T,S就是同一个连通分量了。与假设冲突了. 所以只有出度为0的强连通分量是其他所有点能够互通的。

而如果有多个出度为0的分量。则它们之间是不能互通的。也不存在答案。

分析到这里,直接套tarjan算法缩点求出度即可

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4 + 5;
vector<int> e[maxn];
int dfn[maxn] , low[maxn] , du[maxn] , vis[maxn] , id[maxn] , sz[maxn] , cnt , tot;
stack<int> s;
void tarjan (int u)
{
    dfn[u] = low[u] = ++cnt;
    s.push(u);
    vis[u] = 1;
    for (auto v : e[u]){
        if (!dfn[v]){
            tarjan(v);
            low[u] = min (low[u] , low[v]);
        }
        else if (vis[v]){
            low[u] = min (low[u] , dfn[v]);
        }
    }
    if (dfn[u] == low[u]){
        ++tot;
        while (1){
            int now = s.top();
            s.pop();
            vis[now] = 0;
            id[now] = tot;
            sz[tot]++;
            if (now == u) break;
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    int n , m; cin >> n >> m;
    for (int i = 1 ; i <= m ; i++){
        int x , y;cin >> x >> y;
        e[x].push_back(y);
    }
    for (int i = 1 ; i <= n ; i++){
        if (!dfn[i]) tarjan(i);
    }
    for (int i = 1 ; i <= n ; i++){
        for (auto v : e[i]){
            if (id[i] != id[v]){
                du[id[i]]++;
            }
        }
    }
    int cnt = 0 , res = 0;
    for (int i = 1 ; i <= tot ; i++){
        if (du[i] == 0) {
            cnt++;
            res = sz[i];
        }
    }
    if (cnt >= 2)  cout << "0" << endl;
    else cout << res << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值