图的连通性问题

模板1:缩点

P3387 【模板】缩点 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Tarjan算法+利用并查集维护

主要代码:

​void dfs(int u){
    for(int i = head[u]; i; i = e[i].nt){
        int v = e[i].v;
        if(!vis[v]){//如果没搜索
            vis[v] = vis[u] + 1;//记录“深度”
            dfs(v);//继续搜
        }
        int fu = fd(u), fv = fd(v);//fd 是并查集函数
        if(vis[fv]) vis[fu]<vis[fv]?f[fv]=fu:f[fu]=fv;//深度小的为祖先节点
        }
    }
    vis[u] = -1;//标记搜索完
    return;
}

模板2:割点

P3388 【模板】割点(割顶) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Tarjan算法

首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。

对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。

对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。

主要代码:

void tarjan(int u)
{
    dfn[u]=low[u]=++tot;
    int ts=0;
    for(int i=h[u];i;i=e[i].nt)
    {
        int v=e[i].to;
        if(!dfn[v]){
            ts++;
            f[v]=u;
            tarjan(v);
            low[u]=min(low[v],low[u]);
            if (!f[u]&&ts>1)
            {
                mp[u]++;
            }else if (f[u]&&low[v]>dfn[u])
            {
                mp[u]++;
            }
        }else if(f[u]!=v)
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
}

模板3:出入度的利用

P2746 [USACO5.3]校园网Network of Schools - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

有向图,已知n个点m条边

问题一:最少几个点可以遍历全图?

问题二:最少加几条路可以变成强连通图(图上任意俩点互通)?

问题一解决:入度为0的点的个数;

问题二解决:入度为0的点与出度为0的点的最大值;

如图:

此时最大值为出度为0的点个数3,最少加3条边即可 

 使用构图网站:Graph Editor (csacademy.com)

p2746代码(小心图上只有一个强连通图):

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map>
#define PI 3.1415
#define ll long long
#define mod 1000000007
using namespace std;
int n,f[105],h[105],cnt,dth[105];
int chu[105],ru[105],vis[105],sm1,sm2;
map<int,int>mp;
struct zz
{
    int u,to,nt;
}e[10005];
int fd(int x)
{
    if(x!=f[x])f[x]=fd(f[x]);
    return f[x];
}
void add(int u,int v)
{
    e[++cnt]=zz{u,v,h[u]};
    h[u]=cnt;
}
void tarjan(int u)
{
    for(int i=h[u];i;i=e[i].nt)
    {
        int v=e[i].to;
        if (!dth[v])dth[v]=dth[u]+1,tarjan(v);
        int fu=fd(u),fv=fd(v);
        if(dth[fv]>0)dth[fu]<dth[fv]?f[fv]=fu:f[fu]=fv;
    }
    dth[u]=-1;
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
    	f[i]=i;
        int x;
        cin>>x;
        while (x)
        {
            add(i,x);
            cin>>x;
        }
    }
    for(int i=1;i<=n;i++)
        if(!dth[i])dth[i]=1,tarjan(i);
    for(int i=1;e[i].u!=0;i++)
    {
        int u=e[i].u,v=e[i].to;
        if(f[u]!=f[v]){
            chu[f[u]]++,ru[f[v]]++;
        }
    }
    for(int i=1;i<=n;i++)
    {
    	mp[f[i]]++;
    	if(f[i]!=i)continue;
        if(!chu[i])sm1++;
        if(!ru[i])sm2++;
    }
    cout<<sm2<<endl<< ((mp.size()>1)?max(sm2,sm1):0);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zjhhhzj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值