zoukankan      html  css  js  c++  java
  • 牛客D-Where are you /// kruskal+tarjan找无向图内的环

    题目大意:

    https://ac.nowcoder.com/acm/contest/272/D

    在一个无向图中,给定一个起点,从起点开始走遍图中所有点

    每条边有边权wi,表示第一次经过该道路时的花费(第二次及以后经过时花费为0)

    此时用最少花费完成可能存在多种方案

    求每种方案都必须经过的边有多少条

     

    首先想到最小生成树

    然后想到在得到最短边时 若存在其他长度相等的边 这条边此时就可被替代

    但如果没有长度相等的边 那么这条边就是必须经过的边

    然而这个想法经不起考验 是错误的 如下

     

    但是没有长度相等的边 就是必须经过的边 这是毋庸置疑的

    那么再观察下图

    可以看到这是 一个只由长度相等的边组成的图

    若在这个图中 我们要得到一棵生成树的话 边3-4是必选的 而剩下的1-2、2-3、3-1则任选两条即可

    也就是说在生成树的所有选择方案里 这三条边不是必须经过的边

    那么可以发现 在一个只由长度相等的边组成的图内 能形成一个环的几条边不是在所有的选择方案里必须经过的边

    再结合kruskal得到最小生成树的步骤 每次先得到所有相同长度的最短边建图

    再找到其中的环的个数m 那么要连接所有的环 必须经过的边就有m-1条

    tarjan 求无向图内的环 就是在 有向图求强联通分量 的基础上进行修改

    将 已走过的边 视为有向 不走其反向边

    那么当走完这个图之后 整个图变成了一个有向图 此时图中的强联通分量就是环

    如何 将已走过的边视为有向 呢

    首先建图的过程中 对于一条边 我们是连了正向就连反向的 也就是这两条有向边在存储过程中的序号是连续的

    所以我们从序号2开始存边的话 序号为 2和3 的两条有向边对应一条无向边 4和5对应一条无向边 6和7对应一条......

    则对于 存储顺序为第 x 的有向边 其对应的反向边(即另一条有向边)顺序为 (x^1)

    那么我们在递归时将上一条边的顺序 last 作为参数传过来 不走它对应的反向边即跳过顺序为 last^1 的边 就可以了

    #include <bits/stdc++.h>
    #define mem(i,j) memset(i,j,sizeof(i))
    using namespace std;
    const int N=2e5+5;
     
    struct EDGE {
        int u,v,w;
        bool operator <(const EDGE& p)const {
            return w<p.w;
        }
    }E[N<<1];
    struct NODE { int to,nt; }e[N<<1];
    int head[N], tot;
    void addE(int u,int v) {
        e[++tot].to=v;
        e[tot].nt=head[u];
        head[u]=tot;
    }
    int dfn[N], low[N], ind;
    int fa[N], ans;
    int n, m, p;
    void init_e() {
        ind=0; mem(dfn,0);
        tot=1; mem(head,0);
    }
    void init_s() {
        ans=0;
        for(int i=1;i<=n;i++) fa[i]=i;
    }
     
    int tarjan(int u,int last) {
        dfn[u]=low[u]=++ind;
        int res=0;
        for(int i=head[u];i;i=e[i].nt) {
            if(i==(last^1)) continue; // 是上一条边的对应反向边 跳过
            int v=e[i].to;
            if(!dfn[v]) {
                res+=tarjan(v,i);
                low[u]=min(low[u],low[v]);
            } else low[u]=min(low[u],dfn[v]); 
        }
        if(dfn[u]==low[u]) res++;
        return res;
    }
     
    int getfa(int x) {
        if(x==fa[x]) return x;
        return fa[x]=getfa(fa[x]);
    }
    void unite(int x,int y) {
        x=getfa(x), y=getfa(y);
        if(x!=y) fa[x]=y;
    }
     
    void Kruskal() {
        sort(E,E+m);
        for(int i=0,j;i<m;i=j) {
            j=i;
            while(j<m && E[j].w==E[i].w) j++; //找到所有与最短边相等的边
            init_e(); // 初始化邻接表和tarjan需要的数组
            for(int k=i;k<j;k++) { // 建图
                int u=getfa(E[k].u), v=getfa(E[k].v);
                if(u==v) continue; // 两个点已经连起来了
                addE(u,v); addE(v,u);
            }
            for(int k=i;k<j;k++) {
                int u=getfa(E[k].u);
                if(!dfn[u]) ans+=tarjan(u,0)-1; //保证m个环连通 需要m-1条边
            }
            for(int k=i;k<j;k++)
                unite(E[k].u,E[k].v);
        }
    }
     
    int main()
    {
        while(~scanf("%d%d%d",&n,&m,&p)) {
            init_s();
            for(int i=0;i<m;i++)
                scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
            Kruskal();
            printf("%d
    ",ans);
        }
     
        return 0;
    }
    View Code

     

  • 相关阅读:
    字典树入门
    Cyclic Nacklace HDU 3746 KMP 循环节
    KMP字符串匹配 模板 洛谷 P3375
    Phone List POJ-3630 字典树 or 暴力
    stringstream istringstream ostringstream 三者的区别
    单词数 HDU 2072 字符串输入控制
    逆序单词 HIhoCoder 1366 字典树
    input框中修改placeholder的样式
    如何使用$.each()与$().each()以及他们的区别
    css解决input的阴影
  • 原文地址:https://www.cnblogs.com/zquzjx/p/10054660.html
Copyright © 2011-2022 走看看