模板整理之图论(1)

图论(1)

本篇主要讲解图论中的直径求法,最近公共祖先,割点和桥,连通性
(1)直径求法
分为两种,第一种通过dfs求最大边以及次大边

void dfs(int x)
{
    v[x]=1;
    for (int i=head[x];i;i=e[i].fr)
    {
        int y=e[i].to;
        if (v[y]) continue;
        dfs(y);
        if (d[y]+1>d[x])//如果最大边能被更新 
        {
            d2[x]=d[x],d[x]=d[y]+1;//更新最大边以及次大边 
            c[x]=y;//记录x在直径上的后节点为y 
        }
        else if (d[y]+1>d2[x])//如果次大边能被更新 
        {
            d2[x]=d[y]+1;
        }
    }
}
for (int i=0;i<n;i++) ans=max(ans,d[i]+d2[i]);

第二种为两次bfs,先随便找一个点,求出到这个点最大距离的点p,再以p为起点,寻找与p相距最远的点q,pq即/直径。

void dfs(int x)
{
 v[x]=1;
 for (int i=head[x];i;i=e[i].fr)
 {
  int y=e[i].to;
  if (!v[y])
  {
   if (d[x]+e[i].val>ans)
   {
    ans=d[x]+e[i].val;
    qd=y;
    pre[x]=y;//记录路径
   }
   d[y]=d[x]+e[i].val;
   dfs(y);
  }
 }
}
void qzj()
{
 v[1]=1;
 q.push(1);
 ans=0;
 while (!q.empty())//第一遍bfs
 {
  int x=q.front();
  q.pop();
  for (int i=head[x];i;i=e[i].fr)
  {
   int y=e[i].to;
   if (!v[y])
   {
    if (d[x]+e[i].val>ans)
    {
     ans=d[x]+e[i].val;
     zd=y;
    }
    d[y]=d[x]+e[i].val;
    q.push(y),v[y]=1;
   }
  }
 }
 
 ans=0;
 memset(v,0,sizeof(v));
 memset(d,0,sizeof(d));
 dfs(zd);//第二遍bfs
}

(2)最近公共祖先求法
有两种方法,第一种利用了树上倍增,详细看代码。(这种比较常用好理解)

void dfs(int t)
{
    for (int i=head[t];i;i=e[i].fr)
    {
        int now=e[i].to;
        if (!v[now])
        {
            v[now]=1;
            d[now]=d[t]+1;
            dis[now]=dis[t]+e[i].val;
            f[now][0]=t;//表示now的父节点为t 
            dfs(now);
        }
    }
}
int lca(int x,int y)
{
    if (d[x]>d[y]) swap(x,y);//保证y的深度更大 
    for (int i=15;i>=0;i--)
    {
        if (d[f[y][i]]>=d[x]) y=f[y][i];//使x,y高度相等或差1 
    }
    if (x==y) return x;
    for (int i=15;i>=0;i--)
    {
        if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];//使x,y共同攀升 
    }
    return f[x][0];
}
int main()
{
 
    d[1]=1;//深度 
    v[1]=1;//标记1为已经访问 
    dfs(1);//1为根节点 
    for (int j=1;j<=15;j++)
        for (int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];//外层直接初始化f 
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        int result=dis[x]+dis[y]-2*dis[lca(x,y)];//求两点间最短距离公式 
        printf("%d\n",result);
    }
    return 0;
     
}

第二种利用了tarjan,是并查集对线上标记法的优化,离线算法,所以这里用到vector储存。(因为作者也不是很懂所以暂时略过)

int find(int xx)
{
    if (fa[xx]!=xx) fa[xx]=find(fa[xx]);
    return fa[xx];
}
 
void tarjan(int x)
{
    v[x]=1;
    for (int i=head[x];i;i=e[i].fr)
    {
        int y=e[i].to;
        if (!v[y])
        {
            d[y]=d[x]+e[i].val;
            tarjan(y);//先递归再合并 
            fa[y]=x;
        }
    }
    for (int i=0;i<q[x].size();i++)
    {
        int y=q[x][i],id=qid[x][i];
        if(v[y]==2){
            int lca=find(y);
            ans[id]=d[x]+d[y]-2*d[lca];
        }
    }
    v[x]=2;
}

(3)割点与桥
割点:割了这个点,图被分为不连通的两块以上。
桥:割了这条边,图被分为不连通的两块以上。

因为桥比较重要,这里先讲桥。
(x,y)为桥,则dfn[x]<low[y]!!!(非常重要)
简单来讲就x的 子孙节点 的 最近祖先 没有大于x的。

void dfs(int u,int and_edge)
{
    times++;
    dfn[u]=low[u]=times;//初始化时间戳和u的最近祖先 
    vis[u]=1;
    for (int i=head[u];i;i=e[i].fr)
    {
        int v=e[i].to;
        if (!vis[v])
        {
            dfs(v,i);
            low[u]=min(low[v],low[u]);
            if (low[v]>dfn[u]){//满足桥成立的条件 
                e[i].flag=e[i^1].flag=true; 
            }
        }
        else if (i!=(and_edge^1)) low[u]=min(low[u],dfn[v]);//若已经访问,则更新low[u] 
    }
}
for (int i=1;i<=n;i++)
{
    if(!dfn[i]) dfs(i,0);//防止原始图就不连通 
}

下面讲割点判定法则:dfn【x】<=low【y】注意,分两种情况,第一种若x不为根节点,则一个子节点满足即可。第二种若x为根节点,则必须有两个子节点满足。

void dfs(int u)
{
    times++;
    dfn[u]=low[u]=times;//初始化时间戳和u的最近祖先 
    vis[u]=1;
    int flag=0;//第二种情况
    for (int i=head[u];i;i=e[i].fr)
    {
        int v=e[i].to;
        if (!vis[v])
        {
            dfs(v);
            low[u]=min(low[v],low[u]);
            if (low[v]>=dfn[u]){//满足割点成立的条件 
                flag++;
                if (x!=root || flag>1) cut[u]=true;
            }
        }
        else  low[u]=min(low[u],dfn[v]);//若已经访问,则更新low[u] 
    }
}
for (int i=1;i<=n;i++)
{
    if(!dfn[i]) dfs(i);//防止原始图就不连通 
}

(4)连通问题
连通问题大体分为有向图与无向图的连通。
无向图:分为“点双连通”(无割点),“边双连通”(无桥)。
有向图:强连通分量,即x可以到y,y也可以到x。
下面只对有向图强连通分量进行讲解。

void tarjan(int u)
{
    times++;
    dfn[u]=low[u]=times;//初始化时间戳和u的最近祖先 
    stack[++top]=u;//将u入栈 
    vis[u]=1;
    ins[u]=1;//表示u在栈内 
    int flag=0;
    for (int i=head[u];i;i=e[i].fr)
    {
        int v=e[i].to;
        if (!vis[v])
        {
            tarjan(v);
            low[u]=min(low[v],low[u]);
        }
        else if (ins[v])  low[u]=min(low[u],dfn[v]);//如果v已经在栈内在更新low【u】 
    }
    if (dfs[u]==low[u])//如果构成了一个强联通分量 
 {
  cnt++;
  int y;
  do{
   y=stack[top--];
   ins[y]=0;
   c[y]=cnt;//表示y节点所在强联通分量编号为cnt 
   //scc[cnt].push_back(y);这一步操作可以进行缩点 
  }(y!=u);
  } 
}
for (int i=1;i<=n;i++)
{
    if(!dfn[i]) dfs(i);//防止原始图就不连通 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

19lrf

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

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

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

打赏作者

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

抵扣说明:

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

余额充值