tarjan缩点

tarjan缩点

定义:

强连通:
在有向图G中,如果两个顶点u,v间有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个顶点强连通。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向非强连通图的极大强连通子图,称为强连通分量。

例题:

P3387 【模板】缩点

题意:

求出一条路径,使得这条路径上的点权和最大(无向图),可以重复经过一个点,但点权不会重复加

思路:

我们可以先设想一个 d p i dp_i dpi表示重点为 i i i的最大点权和是多少
转移: d p i = max ⁡ j 所有终点为 i 的边 d p j + a i dp_i = \max_j^{所有终点为i的边}dp_j + a_i dpi=maxj所有终点为i的边dpj+ai
a i 表示 i 的点权 a_i表示i的点权 ai表示i的点权
显然,一个点可以经过最多 n n n次,所以会超时。
我们想到如果若干个点形成了一个强连通分量,那么一点可以把这些点缩成一个点来做,最后形成了一个 D A G DAG DAG(有向无环图),再做 d p dp dp
我们维护两个数组:
1、 d f n i dfn_i dfni 表示 i i i点的时间戳(点 i i i是第几个被访问的点)
2、 l o w i low_i lowi 与点 i i i能到达所有点的时间戳的最小值
把点依次压入栈。
如果某个点的 d f n i = = l o w i dfn_i == low_i dfni==lowi那么这个点和目前在栈中且序号小与它的点一定可以构成一个强连通分量,然后把它们缩成一个点。
每次在图中找出入度为 0 0 0的点来做 d p dp dp(类似于拓扑排序),然后就把相连的边删掉。

code

#include<bits/stdc++.h>
#define LL long long
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
stack<int> stk;
queue<int> que;
const int N = 1e4 + 5 , M = 1e5 + 5;
LL ans , f[N] , w[N];
int hd[N] , hd2[N] , num , cnt2 , cnt , p[N] , dfn[N] , low[N] , a[N] , n , ru[N] , m , b[N] , num1;
struct E {
    int nt , to , fr;
}e[M << 1];
struct EE {
    int nt , to;
}e2[M << 1];
int read () {
    int val = 0 , fu = 1;
    char ch = getchar ();
    while (ch < '0' || ch > '9') {
        if (ch == '-') fu = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9') {
        val = val * 10 + (ch - '0');
        ch = getchar ();
    }
    return val * fu;
}
void add (int x , int y) {
    e[++cnt].to = y , e[cnt].nt = hd[x] , e[cnt].fr = x , hd[x] =cnt;
}
void dfs (int x , int fa) {
    dfn[x] = low[x] = ++num;
    stk.push(x);
    int y;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (!dfn[y]) {
            dfs (y , x);
            low[x] = min (low[x] , low[y]);
        }
        else if (!p[y])
            low[x] = min (low[x] , dfn[y]);
    }
    if (low[x] == dfn[x]) {
        y = 0;
        num1 ++;
        while (y != x && !stk.empty()) {
            y = stk.top();
            stk.pop();
            p[y] = num1;
            w[num1] += a[y];
        }
        f[num1] = w[num1]; 
    }
}
void add2 (int x , int y) { e2[++cnt2].to = y , e2[cnt2].nt = hd2[x] , hd2[x] = cnt2; }
void build () {
    int fa1 , fa2 , x , y;
    fu(i , 1 , cnt) {
        x = p[e[i].fr] , y = p[e[i].to];
        if (x == y) continue;
        add2 (x , y);
        ru[y] ++;
    }
}
void tuo () {
    fu(i , 1 , num1)
        if (!ru[i])
            que.push(i);
    int x , y;
    while (!que.empty()) {
        x = que.front();
        que.pop();
        for (int i = hd2[x] ; i ; i = e2[i].nt) {
            y = e2[i].to;
            ru[y] --;
            if (!ru[y])
                que.push(y);
            f[y] = max (f[y] , f[x] + w[y]);
        }
    }
}
int main () {
    int u , v;
    n = read () , m = read ();
    fu(i , 1 , n) 
        a[i] = read ();
    fu(i , 1 , m) {
        u = read () , v = read ();
        add (u , v);
    }
    fu(i , 1 , n)
        if (!dfn[i])
            dfs (i , 0);
    build ();
    tuo ();
    fu(i , 1 , num)
        ans = max (ans , f[i]);
    printf ("%lld" , ans);
    return 0;
}

雅礼集训1.4 轰炸

【题目描述】

有n座城市,城市之间建立了m条有向的地下通道。

你需要发起若干轮轰炸,每轮可以轰炸任意多个城市。但每次轰炸的城市中,不能存在两个不同的城市i,j满足可以通过地道从城市i到达城市j。

你需要求出最少需要多少轮可以对每座城市都进行至少一次轰炸。

【输入数据】

第一行两个整数n,m。接下来m行每行两个整数a,b表示一条从a连向b的单向边。

【输出数据】

一行一个整数表示答案。

【样例输入】

5 4

1 2

2 3

3 1

4 5

【样例输出】

3

【数据范围】

对于20%的数据,n,m<=10。

对于40%的数据,n,m<=1000。

对于另外30%的数据,保证无环。

对于100%的数据,n,m<=1000000。

分析:

版题

code

#include<bits/stdc++.h>
#define fu(x , y , z) for(int x = y ; x <= z ; x ++)
using namespace std;
stack<int> stk;
queue<int> que;
const int N = 1e6 + 5;
int f[N] , w[N] , cnt , cnt2 , hd[N] , hd2[N] , n , m , ans , dfn[N] , low[N] , num , num1 , p[N] , ru[N];
struct E {
    int to , nt , fr;
}e[N << 1];
struct EE {
    int to , nt;
}e2[N << 1];
void add (int x , int y) { e[++cnt].to = y , e[cnt].fr = x , e[cnt].nt = hd[x] , hd[x] = cnt; }
void add2 (int x , int y) { e2[++cnt2].to = y , e2[cnt2].nt = hd2[x] , hd2[x] = cnt2; }
int read () {
    int val = 0 , fu = 1;
    char ch = getchar ();
    while (ch < '0' || ch > '9') {
        if (ch == '-') fu = -1;
        ch = getchar ();
    }
    while (ch >= '0' && ch <= '9') {
        val = val * 10 + (ch - '0');
        ch = getchar ();
    }
    return val;
}
void dfs (int x) {
    int y;
    stk.push(x);
    dfn[x] = low[x] = ++num;
    for (int i = hd[x] ; i ; i = e[i].nt) {
        y = e[i].to;
        if (!dfn[y]) {
            dfs (y);
            low[x] = min (low[x] , low[y]);
        }
        else if (!p[y])
            low[x] = min (low[x] , dfn[y]);
    }
    if (low[x] == dfn[x]) {
        num1 ++;
        y = 0;
        while (!stk.empty() && y != x) {
            y = stk.top();
            stk.pop();
            p[y] = num1;
            w[num1] ++;
        }
        f[num1] = w[num1];
    }
}
void build () {
    int x , y;
    fu(i , 1 , cnt) {
        x = p[e[i].fr] , y = p[e[i].to];
        if (x == y) continue;
        add2 (x , y);
        ru[y] ++;
    }
}
void tuo () {
    int x , y;
    fu(i , 1 , num1) {
        if (!ru[i])
            que.push(i);
    }
    while (!que.empty()) {
        x = que.front();
        que.pop();
        for (int i = hd2[x] ; i ; i = e2[i].nt){
            y = e2[i].to;
            ru[y] --;
            if (!ru[y]) que.push(y);
            f[y] = max (f[y] , f[x] + w[y]);
        }
    }
}
int main () {
    int u , v;
    n = read () , m = read ();
    fu(i , 1 , m) {
        u = read () , v = read ();
        add (u , v);
    }
    fu(i , 1 , n) {
        if (!dfn[i])
            dfs (i);
    }
    build ();
    tuo ();
    fu(i , 1 , num1)
        ans = max (ans , f[i]);
    printf ("%d" , ans);
    return 0;
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值