【知识】Tarjan 算法

Tarjan

P8435 【模板】点双连通分量

#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << " = " << x << endl
#define ll long long
typedef pair <int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
//#define int long long
inline int rd(){
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x){
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x){
    putchar(x);
}
void wt(int x, char k){
    wt(x),putchar(k);
}

namespace Star_F{
    const int N = 500005, M = 4000005;
    int h[N], e[M], ne[M], idx;
    int s[N], cnt, top, bcc, low[N], dfn[N];
    int n, m;
    vector<int> ans[N];

    void add(int u,int v){
        e[++idx] = v, ne[idx] = h[u], h[u] = idx;
    }

    void tarjan(int u,int fa){
        int son = 0;
        low[u] = dfn[u] = ++cnt;
        s[++top] = u;
        for (int i = h[u]; i;i=ne[i]){
            int v = e[i];
            if(!dfn[v]){
                son++;
                tarjan(v, u);
                low[u] = min(low[u], low[v]);
                if(low[v]>=dfn[u]){
                    bcc++;
                    while(s[top+1]!=v)
                        ans[bcc].push_back(s[top--]);
                    ans[bcc].push_back(u);
                }
            }
            else if(v!=fa)
                low[u] = min(low[u], dfn[v]);
        }
        if(fa==0&&son==0)
            ans[++bcc].push_back(u);
    }
    void Main(){
        cin >> n >> m;
        for (int i=1; i <= m;i++){
            int u, v;
            cin >> u >> v;
            add(u, v), add(v, u);
        }
        for (int i = 1; i <= n;i++){
            if(!dfn[i])
                top = 0, tarjan(i, 0);
        }
        cout << bcc << endl;
        for (int i = 1; i <= bcc;i++){
            cout << ans[i].size() << " ";
            for(int j:ans[i])
                cout << j << " ";
            cout << endl;
        }
    }

}

signed main(){
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T=1;
    // T=rd;
    while(T--) Star_F::Main();
    // ClockB;
    return 0;
}


/*
*          ▀▀▀██████▄▄▄       _______________
*          ▄▄▄▄▄  █████████▄  /                 \
*         ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Code has no BUG!  |
*      ▀▀█████▄▄ ▀██████▄██ | _________________/
*       ▀▄▄▄▄▄  ▀▀█▄▀█════█▀ |/
*            ▀▀▀▄  ▀▀███ ▀       ▄▄
*         ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌   ______________________________
*       ██▀▄▄▄██▀▄███▀ ▀▀████      ▄██  █                               \\
*    ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███     ▌▄▄▀▀▀▀█_____________________________ //
*    ▌    ▐▀████▐███▒▒▒▒▒▐██▌
*    ▀▄▄▄▄▀   ▀▀████▒▒▒▒▄██▀
*              ▀▀█████████▀
*            ▄▄██▀██████▀█
*          ▄██▀     ▀▀▀  █
*         ▄█             ▐▌
*     ▄▄▄▄█▌              ▀█▄▄▄▄▀▀▄
*    ▌     ▐                ▀▀▄▄▄▀
*     ▀▀▄▄▀     ██
* \  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
* \- ▌           Name: Star_F              ▀ ▀
*  - ▌                            (o)          ▀
* /- ▌            Go Go Go !               ▀ ▀
* /  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
*/

P8436 【模板】边双连通分量

#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const int N = 500010, M = 2000010 * 2;

int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int id[N], dcc_cnt;
bool is_bridge[M];
int tot[N];
vector <int> ans[N];

void add(int a, int b)
{
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx ++ ;
	return;
}

void tarjan(int u, int from)
{
	dfn[u] = low[u] = ++ timestamp;
	stk[ ++ top] = u;
	
	for (int i = h[u]; ~i; i = ne[i])
	{
		int j = e[i];
		if (!dfn[j])
		{
			tarjan(j, i);
			low[u] = min(low[u], low[j]);
			if (low[j] > dfn[u])
				is_bridge[i] = is_bridge[i ^ 1] = true;
		}
		else if (i != (1 ^ from))
			low[u] = min(low[u], dfn[j]);
	}
	
	if (dfn[u] == low[u])
	{
		dcc_cnt ++ ;
		int y;
		do
		{
			y = stk[top -- ];
			id[y] = dcc_cnt;
		} while (y != u);
	}
	
	return;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> m;
	memset(h, -1, sizeof h);
	for (int i = 1; i <= m; i ++ )
	{
		int a, b;
		cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	
	for (int i = 1; i <= n; i ++ )
		if (!dfn[i]) tarjan(i, -1);
	
	cout << dcc_cnt << endl;
	
	for (int i = 1; i <= n; i ++ )
	{
		tot[id[i]] ++ ;
		ans[id[i]].push_back(i);
	}
	
	int p = 1;
	while (tot[p])
	{
		cout << tot[p] << ' ';
		for (int i = 0; i < ans[p].size(); i ++ )
			cout << ans[p][i] << ' ';
		p ++ ;
		cout << endl;
	}
	
	return 0;
}

P3388 【模板】割点(割顶)

#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 5;
int n, m, num,root;
int dn, dfn[N], low[N], cnt, buc[N],sum; 
vector<int> e[N];
void Tarjan(int x){
	dfn[x]=low[x]=++num;
	int flag=0;
	for(int i=0;i<e[x].size();i++){
		int to=e[x][i];
		if(!dfn[to]){
			Tarjan(to);
			low[x]=min(low[x],low[to]);
			if(low[to]>=dfn[x]){
				flag++;
				if(x!=root||(flag>=2)){
					buc[x]=1;
				}
			}
		}
		else low[x]=min(low[x],dfn[to]);
	}
}
int main() {
	cin >> n >> m;
	for(int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]){
			root=i;
			Tarjan(i);
		} 
	}
	for(int i=1;i<=n;i++){
		if(buc[i]) sum++; 
	} 
	cout<<sum<<endl;
	for(int i=1;i<=n;i++){
		if(buc[i]) cout<<i<<" ";
	} 
	return 0;
}

P3469 [POI 2008] BLO-Blockade

对于每个点,考虑将其删除对图会产生什么影响

  • 不是割点:答案就是 2(n1)

  • 是割点:删除该割点产生 k 个联通块,则答案就是 2(n1)+i=1kj=1,jiksizei×sizej

考虑优化,显然 j=1,jiksizej=nsizei1

所以答案就是 2×((n1)+i=1ksizei×(nsizei1)),因为是无序的

#include <bits/stdc++.h>
using namespace std;

#define FOR(i, a, b) for (int i = (a); i <= (b); ++i)
#define ROF(i, a, b) for (int i = (a); i >= (b); --i)
#define DEBUG(x) cerr << #x << '=' << x << endl
typedef pair<int, int> PII;
typedef unsigned int uint;
typedef unsigned long long ull;
#define i128 __int128
#define fi first
#define se second
mt19937 rnd(chrono::system_clock::now().time_since_epoch().count());
#define ClockA clock_t start, end; start = clock()
#define ClockB end = clock(); cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
#define int long long
inline int rd() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
    return x * f;
}
#define rd rd()

void wt(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) wt(x / 10);
    putchar(x % 10 + '0');
    return;
}
void wt(char x) {
    putchar(x);
}
void wt(int x, char k) {
    wt(x), putchar(k);
}

namespace Star_F {
    const int N = 100005, M = 500005;
    int n, m, h[N], dfn[N], low[N];
    int sz[N], ans[N];

    struct Edge {
        int nxt, to;
    };
    Edge E[M << 1];

    int tot;
    void add(int u, int v) {
        E[++tot].nxt = h[u];
        E[tot].to = v;
        h[u] = tot;
    }

    int cnt;
    void tarjan(int u) {
        dfn[u] = low[u] = ++cnt;
        int  sum = 0;
        sz[u] = 1;
        for (int i = h[u]; i; i = E[i].nxt) {
            int v = E[i].to;
            if (!dfn[v]) {
                tarjan(v);
                sz[u] += sz[v];
                if (low[v] >= dfn[u]) {
                    ans[u] += sz[v] * sum; 
                    sum += sz[v];
                }
                low[u] = min(low[u], low[v]);
            } else {
                low[u] = min(low[u], dfn[v]);
            }
        }
        ans[u] += (n - sum - 1) * sum; 
        ans[u] += n - 1;
    }

    void Main() {
        n = rd, m = rd;
        FOR(i, 1, m) {
            int u = rd, v = rd;
            add(u, v);
            add(v, u);
        }
        tarjan(1);
        FOR(i, 1, n) {
            wt(ans[i] * 2,'\n'); 
        }
    }
}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    ClockA;
    int T = 1;
    // T = rd; 
    while (T--) Star_F::Main();
    // ClockB;
    return 0;
}

/*
*          ▀▀▀██████▄▄▄       _______________
*          ▄▄▄▄▄  █████████▄  /                 \
*         ▀▀▀▀█████▌ ▀▐▄ ▀▐█ | Code has no BUG!  |
*      ▀▀█████▄▄ ▀██████▄██ | _________________/
*       ▀▄▄▄▄▄  ▀▀█▄▀█════█▀ |/
*            ▀▀▀▄  ▀▀███ ▀       ▄▄
*         ▄███▀▀██▄████████▄ ▄▀▀▀▀▀▀█▌   ______________________________
*       ██▀▄▄▄██▀▄███▀ ▀▀████      ▄██  █                               \\
*    ▄▀▀▀▄██▄▀▀▌████▒▒▒▒▒▒███     ▌▄▄▀▀▀▀█_____________________________ //
*    ▌    ▐▀████▐███▒▒▒▒▒▐██▌
*    ▀▄▄▄▄▀   ▀▀████▒▒▒▒▄██▀
*              ▀▀█████████▀
*            ▄▄██▀██████▀█
*          ▄██▀     ▀▀▀  █
*         ▄█             ▐▌
*     ▄▄▄▄█▌              ▀█▄▄▄▄▀▀▄
*    ▌     ▐                ▀▀▄▄▄▀
*     ▀▀▄▄▀     ██
* \  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
* \- ▌           Name: Star_F              ▀ ▀
*  - ▌                            (o)          ▀
* /- ▌            Go Go Go !               ▀ ▀
* /  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀
*/

P2860 [USACO06JAN] Redundant Paths G

注意到答案为缩点后叶子节点的个数 +1 除以 2,Tarjan 缩点求答案即可。

由于是边双,一条边 i 的反向边就是 i1,所以链式前向星的 idx1 开始。

#include <bits/stdc++.h>
using namespace std;
const int N = 5005, M = 100005;
int n, m, vis[N << 1], du[N], ans;
int h[N], e[N], ne[N], idx=1;
int u[M], v[M], id[N];
int dfn[N], low[N], s[N], col, top, now;
void add(int u,int v){
    e[++idx] = v, ne[idx] = h[u], h[u] = idx;
}

void tarjan(int u){
    dfn[u] = low[u] = ++now;
    s[++top] = u;
    for (int i = h[u]; i; i=ne[i]){
        int v = e[i];
        if(!vis[i]){
            vis[i] = vis[i ^ 1] = 1;
            if(!dfn[v]){
                tarjan(v);
                low[u] = min(low[u], low[v]);
            }
            else
                low[u] = min(low[u], dfn[v]);
        }
    }
    if(low[u]==dfn[u]){
        id[u] = ++col;
        while(s[top]!=u)
            id[s[top--]] = col;
        top--;
    }
}

int main(){
    cin >> n >> m;
    for (int i = 1; i <= m;i++){
        cin >> u[i] >> v[i];
        add(u[i], v[i]), add(v[i], u[i]);
    }
    for (int i = 1; i <= n;i++){
        if(!dfn[i])
            tarjan(i);
    }
    for (int i = 1; i <= m;i++){
        if(id[u[i]]!=id[v[i]])
            du[id[u[i]]]++, du[id[v[i]]]++;
    }
    for (int i = 1; i <= col;i++)
        ans += du[i] == 1;
    cout << (ans + 1 >> 1) << endl;
    return 0;
}

P5058 [ZJOI2004] 嗅探器

题意:从 xy 简单路径上经过的最小的割点是多少。

其实就是板子题,只需要再 Tarjan 中加上 low[y] >= dfn[x] && dfn[b] >= dfn[y] 即可。

#include <bits/stdc++.h>
using namespace std;
int n, m, a, b;
const int N = 210000;
vector<int> v[N];
int dfn[N], low[N], step, fa[N], son[N], cut[N];
void tarjan(int x) {
	dfn[x] = low[x] = ++step;
	for (int i = 0; i < v[x].size(); i++) {
		int y = v[x][i];
		if (y == fa[x]) continue;
		if (!dfn[y]) {
			fa[y] = x;
			son[x]++;
			tarjan(y);
			if (low[y] >= dfn[x] && dfn[b] >= dfn[y])
				cut[x] = 1;
			low[x] = min(low[x], low[y]);
		} else
			low[x] = min(low[x], dfn[y]);
	}
	if (son[x] == 1 && fa[x] == 0)
		cut[x] = 0;
}
int main() {
    cin >> n;
    while (1) {
		int x, y;
        cin >> x >> y;
        if (!(x + y))
			break;
		v[x].push_back(y);
		v[y].push_back(x);
	}
    cin >> a >> b;
    tarjan(a);
	for (int i = 1; i <= n; i++) {
		if (i == a)
			continue;
		if (cut[i])
			cout<<i, exit(0);
	}
    cout << "No solution" << endl;
}

P3225 [HNOI2012] 矿场搭建

首先先缩点,然后分类讨论。

  • 没有割点的块,所以需要在块内两个不同节点上建两个出口(任意一个塌了可以用另外一个)

    还要特判一下:如果只有一个点的话建一个出口就行。

  • 有一个割点的块,这种块要在除割点外任意一点建一个出口,这样如果割点塌了,可以用那个出口,如果出口塌了可以通过割点去到别的块。

  • 有两个或以上割点的块,这种块是不需要出口的,不难发现,不管哪个点塌了,都可以通过没塌的割点去到别的块。

#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

typedef unsigned long long ULL;

const int N = 1010, M = 1010;

int n, m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
int dcc_cnt;
vector<int> dcc[N];
bool cut[N];
int root;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ timestamp;
    stk[ ++ top] = u;

    if (u == root && h[u] == -1)
    {
        dcc_cnt ++ ;
        dcc[dcc_cnt].push_back(u);
        return;
    }

    int cnt = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            tarjan(j);
            low[u] = min(low[u], low[j]);
            if (dfn[u] <= low[j])
            {
                cnt ++ ;
                if (u != root || cnt > 1) cut[u] = true;
                ++ dcc_cnt;
                int y;
                do {
                    y = stk[top -- ];
                    dcc[dcc_cnt].push_back(y);
                } while (y != j);
                dcc[dcc_cnt].push_back(u);
            }
        }
        else low[u] = min(low[u], dfn[j]);
    }
}

int main()
{
    int T = 1;
    while (cin >> m, m)
    {
        for (int i = 1; i <= dcc_cnt; i ++ ) dcc[i].clear();
        idx = n = timestamp = top = dcc_cnt = 0;
        memset(h, -1, sizeof h);
        memset(dfn, 0, sizeof dfn);
        memset(cut, 0, sizeof cut);

        while (m -- )
        {
            int a, b;
            cin >> a >> b;
            n = max(n, a), n = max(n, b);
            add(a, b), add(b, a);
        }

        for (root = 1; root <= n; root ++ )
            if (!dfn[root])
                tarjan(root);

        int res = 0;
        ULL num = 1;
        for (int i = 1; i <= dcc_cnt; i ++ )
        {
            int cnt = 0;
            for (int j = 0; j < dcc[i].size(); j ++ )
                if (cut[dcc[i][j]])
                    cnt ++ ;

            if (cnt == 0)
            {
                if (dcc[i].size() > 1) res += 2, num *= dcc[i].size() * (dcc[i].size() - 1) / 2;
                else res ++ ;
            }
            else if (cnt == 1) res ++, num *= dcc[i].size() - 1;
        }

        printf("Case %d: %d %llu\n", T ++, res, num);
    }

    return 0;
}

CF51F Caterpillar

[Alex_Wei](CF51F Caterpillar - 洛谷专栏)

posted @   Star_F  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示