【图论专题】有向图的强连通分量

整理的算法模板合集: ACM模板


题目列表:

题目 算法
A、AcWing 1174. 受欢迎的牛 缩点
B、AcWing 367. 学校网络 缩点
C、AcWing 1175. 最大半连通子图 tarjan缩点 + DP + hash
D、AcWing 368. 银河 tarjan缩点 / 差分约束

【图论专题】有向图的强连通分量_第1张图片
【图论专题】有向图的强连通分量_第2张图片
【图论专题】有向图的强连通分量_第3张图片

A、AcWing 1174. 受欢迎的牛

【图论专题】有向图的强连通分量_第4张图片
tarjan缩点模板题。
需要注意的是我们缩完点以后,要查找出度和入度的时候应该遍历所有的边,并看这个边的出点和入点来自哪一个缩完点以后的点,这个点的出度或者入度++。或者缩点之后建图,跑新图也可以

#include
#include
#include
#include
#include

using namespace std;

const int N = 50007, M = 500007, INF = 0x3f3f3f3f;
int n,m;
int dfn[N], low[N];
int ins[N];
int ver[M], edge[M], nex[M],head[N], tot;

vector<int>scc[N];
int num;
int stk[N],top;
int scc_cnt;
int c[N];
int out[N];
int scc_num[N];

void add(int x,int y){
    ver[tot] = y;
    nex[tot] = head[x];
    head[x] = tot ++ ;
}

int v[M],h[M],ne[M],tc;

void add_c(int x,int y){
    v[tc] = y;
    ne[tc] = h[x];
    h[x] = tc ++ ;
}

void tarjan(int x){
    dfn[x] = low[x] = ++num;
    ins[x]  = 1;
    stk[ ++ top] = x;
    for(int i = head[x];~i;i = nex[i]){
        int j = ver[i];
        if(!dfn[j]){
            tarjan(j);
            low[x] = min(low[j],low[x]);
        }
        else if(ins[j]){
            low[x] = min(low[x],dfn[j]);
        }
    }
    if(dfn[x] == low[x]){
        scc_cnt ++ ;
        int y;
        do{
            y = stk[top -- ];
            ins[y] = 0;
            c[y] = scc_cnt;
            scc[scc_cnt].push_back(y);
            scc_num[scc_cnt] ++;
        }while(x != y);
    }
}


int main(){
    memset(head,-1,sizeof head);
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;++i){
        int a, b;
        scanf("%d%d",&a, &b);
        add(a, b);
    }
    for(int i = 1;i <= n;++i)
        if(!dfn[i])
            tarjan(i);

    for(int x = 1;x <= n;++x){
        for(int i = head[x];~i;i = nex[i]){
            int y = ver[i];
            if(c[x] == c[y])continue;
            add_c(c[x],c[y]);
            out[c[x]] ++ ;
        }
    }
    
    int ans = 0;

    for(int i = 1;i <= scc_cnt;++i){
        if(!out[i]){
            if(!ans)
                ans = i;
            else {puts("0");return 0;}
        }
    }
    printf("%d\n",scc_num[ans]);
    return 0;
}

B、AcWing 367. 学校网络

【图论专题】有向图的强连通分量_第5张图片

首先,可以进行缩点,这样缩点之后就变成了一个有向无环图(DAG),满足拓扑序。
子任务1:缩点后入度为零的强连通分量必须要有新软件.
子任务2:要求加边后形成一个强连通图。可以考虑到缩点后的DAG上每个点都必须同时具有入度和出度,就可以将没有入度的点的数量记为p,没有出度的点的数量记为q;由于没有出度的点可以直接连接没有入度的点,答案即为max(p,q).
正确性显然.
为了避免混淆,所以我这里缩完点以后就直接建了一个新图,然后之后所有的操作都在新图中进行。

#include
#include
#include
#include

using namespace std;

const int N = 5000007, M = 50000007, INF = 0x3f3f3f3f;

typedef long long ll;

int n, m;
int dfn[N], low[N], num;
int ins[N], stk[N], top;
int head[N], edge[M], nex[N], ver[N], tot;
int hs[N];
int scc_cnt, Size[N], scc_id[N];
int out[N], in[N];

void add(int h[], int x, int y )
{
    ver[tot] = y;
    nex[tot] = h[x];
    h[x] = tot ++ ;
}

void tarjan(int x)
{
    dfn[x] = low[x] = ++num;
    stk[++ top] = x;
    ins[x] = true;
    for(int i = head[x];~i ;i = nex[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y);
            low[x] = min(low[x], low[y]);
        }
        else if(ins[y])
            low[x] = min(low[x], dfn[y]);
    }

    if(dfn[x] == low[x]){
        ++scc_cnt;
        int y;
        do{
            y = stk[top -- ];
            ins[y] = false;
            scc_id[y] = scc_cnt;
            Size[scc_cnt] ++;
        }while(x != y);
    }
}

int main()
{
    memset(head,-1,sizeof head);
    memset(hs,-1,sizeof hs);
    scanf("%d",&n);
    for(int i = 1;i <= n;++i){
        int b;
        while(~scanf("%d",&b) && b){
            add(head,i,b);
        }
    }

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

    for(int x = 1;x <= n;++x)
    {
        for(int i = head[x];~i;i = nex[i])
        {
            int y = ver[i];
            int a = scc_id[x],b = scc_id[y];
            if(a != b){
                add(hs,a,b);
            }
        }
    }

    for(int x = 1;x <= scc_cnt;++x){
        for(int i = hs[x];~i;i = nex[i])
        {
            int y = ver[i];
            out[x] ++ ;
            in[y] ++ ;
        }
    }

    int maxo = 0;
    int maxi = 0;
    for(int x = 1;x <= scc_cnt;++x)
    {
        if(!out[x])maxo ++ ;
        if(!in[x])maxi ++ ;
    }
    printf("%d\n",maxi);
    if(scc_cnt == 1)
        puts("0");
    else printf("%d\n",max(maxo,maxi));

    return 0;
}

C、AcWing 1175. 最大半连通子图(tarjan缩点 + DP + hash)

【图论专题】有向图的强连通分量_第6张图片

这时yxc上课时讲解的截图。
在这里插入图片描述
一般用到tarjan算法的题目步骤都非常相似:

  1. tarjan算法
  2. 缩点,建图(这里要判重)
  3. 按照拓扑序递推(这里缩点以后逆向就已经是拓扑序了)/ 循环遍历新图求解答案。

导出子图:点是原图的子集,边一定包含与子图中所有点有关的边。
半联通子图:所有的两个点,我要么可以直接一条路过去,要么你可以直接过来。
显然,对于任意一个强连通分量 S ∈ G S\in G SG,它一定一个半连通子图,于是我们可以考虑先缩点。缩点之后,原图变成了一个有向无环图,显然,对于任意一条DAG中的一条单向到达的链,它都是一个半连通子图。

所以答案K就是DAG中最大链的顶点个数,答案C就是DAG中不同的最大链有多少种。
由于整个DAG按顺序已经是一个拓扑序了,所以我们可以直接递推DP线性 O ( n ) O(n) O(n)求答案。 设size(u)表示顶点u代表的强连通分量
S中的顶点个数,f(u)表示从顶点u开始延伸的最大链的顶点个数,g(u)表示从顶点u开始延伸的不同的最大链有多少个,则有:
f ( u ) = s i z e ( u ) + m a x ( f ( v ) )      ( u → v ) f(u)=size(u)+max(f(v))\ \ \ \ (u\to v) f(u)=size(u)+max(f(v))    (uv)

g ( u ) = ∑ g ( v ) ( u → v , s i z e ( u ) + f ( v ) = f ( u ) ) g(u)=∑g(v)(u→v,size(u)+f(v)=f(u)) g(u)=g(v)(uv,size(u)+f(v)=f(u))每次从一个没有被访问过的顶点开始做深度优先搜索,回溯时计算每个顶点的f,g值。K,C也可以按照相同的方式进行更新。

本题需要判重去重,因为第二问是总的方案数,如果边是一摸一样的那么方案应该是同一种,所以如果不判重就会影响最后的答案。我们直接用一个set判重,顺便用到一个巧妙的hash,因为我们一共就只有100000个点,所以我们直接把所有的点都映射到100000+v即可,不会产生冲突。
需要注意的是建完的图是逆拓扑序,按照拓扑序递推DP,所以必须倒着来循环DP。

#include
#include
#include
#include
#include

using namespace std;

const int N = 100007, M = 2000007, INF = 0x3f3f3f3f;

typedef long long ll;
int mod;
int n, m;
int dfn[N], low[N], num;
int ver[M], edge[M], head[N], nex[M], tot;
int h[N];

int Size[N], scc_cnt, scc_id[N];
int stk[N], top, ins[N];

int f[N], g[N];//顶点个数和方案数

void add(int h[],int x,int y){
    ver[tot] = y;
    nex[tot] = h[x];
    h[x] = tot ++ ;
}

void tarjan(int x){
    dfn[x] = low[x] = ++ num;
    stk[ ++ top] = x;
    ins[x] = true;

    for(int i = head[x];~i;i = nex[i]){
        int y = ver[i];
        if(!dfn[y]){
            tarjan(y);
            low[x] = min(low[x],low[y]);
        }
        else if(ins[y]){
            low[x] = min(low[x], dfn[y]);
        }
    }

    if(dfn[x] == low[x]){
        int y;
        ++ scc_cnt;
        do{
            y = stk[top -- ];
            ins[y] = false;
            scc_id[y] = scc_cnt;
            Size[scc_cnt] ++ ;
        }while(x != y);
    }
}

int main()
{
    memset(head,-1,sizeof head);
    memset(h,-1,sizeof h);
    scanf("%d%d%d",&n,&m,&mod);
    while(m -- ){
        int x,y;
        scanf("%d%d",&x,&y);
        add(head,x,y);
    }

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

    unordered_set<ll>st;
    //(u,v) -> u * 1000000 + v;
    //因为一共只有100000个点
    for(int i = 1;i <= n;++i){
        for(int j = head[i];~j;j = nex[j]){
            int k = ver[j];
            int a = scc_id[i], b = scc_id[k];
            ll hash = a * 100000ll + b;
            //建新图并判重
            if(a != b && !st.count(hash)){
                add(h,a,b);
                st.insert(hash);
            }
        }
    }

    for(int i = scc_cnt;i >= 1;-- i){//建完的图是逆拓扑序,按照拓扑序递推,所以必须倒着来
        if(!f[i]){
            f[i] = Size[i];
            g[i] = 1;
        }
        for(int j = h[i];~j;j = nex[j]){
            int k = ver[j];
            if(f[k] < f[i] + Size[k]){
                f[k] = f[i] + Size[k];
                g[k] = g[i];
            }
            else if(f[k] == f[i] + Size[k]){
                g[k] = (g[k] + g[i]) % mod;
            }
        }
    }

    ll maxx = 0,sum = 0;
    for(int i = 1;i <= n;++i){
        if(f[i] > maxx){
            maxx = f[i];
            sum = g[i];
        }
        else if(f[i] == maxx){
            sum = (sum + g[i] ) % mod;
        }
    }
    printf("%lld\n%lld\n",maxx, sum);
    return 0;
}

D、AcWing 368. 银河 (tarjan缩点 / 差分约束)

【图论专题】有向图的强连通分量_第7张图片
本题与之前的差分约束的那一道糖果题一摸一样,甚至连数据都是一样的。数据实际上比较大,我们可以用差分约束,并将spfa的队列换成栈即可避免超时。

【图论专题】有向图的强连通分量_第8张图片
但是本题我们可以用tarjan缩点,线性解决这一问题。
【图论专题】有向图的强连通分量_第9张图片

#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 100010, M = 600010;

int n, m;
int h[N], hs[N], e[M], ne[M], w[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, size[N];
int dist[N];

void add(int h[], int a, int b, int c)
{
    e[idx] = b, w[idx] = c, 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()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    memset(hs, -1, sizeof hs);

    for (int i = 1; i <= n; i ++ ) add(h, 0, i, 1);

    while (m -- )
    {
        int t, a, b;
        scanf("%d%d%d", &t, &a, &b);
        if (t == 1) add(h, b, a, 0), add(h, a, b, 0);
        else if (t == 2) add(h, a, b, 1);
        else if (t == 3) add(h, b, a, 0);
        else if (t == 4) add(h, b, a, 1);
        else add(h, a, b, 0);
    }

    tarjan(0);

    bool success = true;
    for (int i = 0; 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)
            {
                if (w[j] > 0)
                {
                    success = false;
                    break;
                }
            }
            else add(hs, a, b, w[j]);
        }
        if (!success) break;
    }

    if (!success) puts("-1");
    else
    {
        for (int i = scc_cnt; i; i -- )
        {
            for (int j = hs[i]; ~j; j = ne[j])
            {
                int k = e[j];
                dist[k] = max(dist[k], dist[i] + w[j]);
            }
        }

        LL res = 0;
        for (int i = 1; i <= scc_cnt; i ++ ) res += (LL)dist[i] * size[i];

        printf("%lld\n", res);
    }

    return 0;
}

你可能感兴趣的:(#,第三章,图论,#,有向图的强连通分量,AcWing算法提高课)