强连通分量

基础介绍

连通分量:对于分量中任意两点两点u,v;必然可以从u走到v,且从v走到u。

强连通分量:极大连通分量。

将任意一个有向图通过缩点的方式转化为有向无环图(拓扑图)(DAG)

好处:求最短路最长路可以递推来求解

如何求强连通分量

在DFS过程中
1.树枝边(x,y)
2.前向边(x,y)
3.后向边(x,y)
4.横插边(x,y)

判断某个点是否在某个强连通分量(scc)中?
情况1 存在一条后向边走到祖先节点
情况2 存在一条横叉边,横插边再走到祖先节点

Tarjan 求 scc

时间戳:
对于每个点定义两个时间戳
dfn[u]遍历到u的时间
low[u]从u开始走,所能遍历到的最小的时间戳

求每个强连通分量最高点
dfn[u] == low[u]

代码模板

void tarjan(int u)
{
dfs[u] = low[u] = ++ timestamp;
stk[++ top] = u,in_stk[u] = true;//栈
for(int i = h[u];~i;i=ne[i])
{
int j = e[i];
if(!dfs[j])
{
    tarjan(j);
    low[u] = min(low[u],low[j]);
}
else if(in_stk[j])
    low[u] = min(low[u],low[j]);
}

    if(dfn[u] == low[u])
    {
     int y;
     ++ scc_cnt;
     do
     {
       y = stk[top--];
       in_stk[y] = false; //出栈标记
       id[y] = scc_cnt;
     }while(y!=u);

    }
}

//stk 当前还没有遍历完成强连通分量的所有点

题单

受欢迎的牛

题目传送门_受欢迎的牛:https://www.luogu.com.cn/problem/P2341

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;;

const int N = 1e5 + 10,M = 5e5 + 10;

int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
int id[N],scc_cnt,Size[N];
int dout[N];
bool in_stk[N];

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;
    in_stk[u] = true;
    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]);
        }
        else if(in_stk[j]) low[u] = min(low[u],dfn[j]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top --];
            in_stk[y] = false;
            id[y] = scc_cnt;
            Size[scc_cnt] ++ ;
        }while(y != u);
    }
}

int main()
{
    cin >> n >> m;
    memset(h,-1,sizeof h);
    while(m --)
    {
        int a,b;
        cin >> a >> b;
        add(a,b);
    }
    
    for(int i = 1;i <= n;i ++)
    if(!dfn[i]) tarjan(i);
    
    for(int i = 1;i <= n;i ++)
    for(int j = h[i];~j;j = ne[j])
    {
        int k = e[j];
        int a = id[i],b = id[k];
        if(a != b) dout[a] ++;
    }
    
    int zeros = 0;
    int sum = 0;
    for(int i = 1;i <= scc_cnt;i ++)
    if(!dout[i])
    {
        zeros ++;
        sum += Size[i];
        if(zeros > 1)
        {
            sum = 0;
            break;
        }
    }
    
    cout << sum << endl;
    return 0;
}

间谍网络(70分代码)

题目传送门_间谍网络:https://www.luogu.com.cn/problem/P1262

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;;

const int N = 1e5 + 10,M = 5e5 + 10;
const int INF = 0x3f3f3f3f;

int n,r;
int p;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
int id[N],scc_cnt;
bool is_ok[N];
int scc_money[N];
int min_id[N];
int din[N];


bool in_stk[N];
int buy[N];
int list[N];

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;
    in_stk[u] = true;
    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]);
        }
        else if(in_stk[j]) low[u] = min(low[u],dfn[j]);
    }
    
    if(dfn[u] == low[u])
    {
        ++ scc_cnt;
        int y;
        do {
            y = stk[top --];
            in_stk[y] = false;
            id[y] = scc_cnt;
            if(buy[y]) is_ok[scc_cnt]= true;
            scc_money[scc_cnt] = min(buy[y],scc_money[scc_cnt]);
            min_id[scc_cnt] = min(min_id[scc_cnt],y);
            
        }while(y != u);
    }
}

int main()
{
    memset(is_ok,false,sizeof is_ok);
    memset(scc_money,INF,sizeof scc_money);
    memset(min_id,INF,sizeof min_id);
    cin >> n;
    cin >> p;
    for(int i = 0;i < p;i ++)
    {
        cin >> list[i];
        cin >> buy[list[i]];
    }
    cin >> r;
    memset(h,-1,sizeof h);
    while(r --)
    {
        int a,b;
        cin >> a >> b;
        add(a,b);
    }
    
    for(int i = 1;i <= n;i ++)
    if(!dfn[i]) tarjan(i);
    for(int i = 1;i <= n;i ++)
        for(int j = h[i];~j;j = ne[j])
        {
            int k = e[j];
            int a = id[i],b = id[k];
            if(a!=b) din[b]++;
        }
    
    
    bool flag = true;
    for(int i = 1;i <= scc_cnt;i++)
            if(!din[i])
            flag *= is_ok[i];
    
    if(flag)
    {
        long long sum = 0;
        for(int i = 1;i <= scc_cnt;i ++)
            if(!din[i])
            sum += scc_money[i];
        cout << "YES" << endl;
        cout << sum << endl;
    }
    else
    {
        int minid = 1e9;
        for(int i = 1;i <= scc_cnt;i ++)
            if((!din[i])&&(!is_ok[i]))
                minid = min(min_id[i],minid);
        cout << "NO" << endl;
        cout <<minid << endl;
    }
    
    return 0;
}




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值