• 图论总结


    • 图论总结

    • 补图

      P3452[POI2007]BIU-Offices

        题意:给出一个图(N个点,M条边),让你把此图分成尽可能多的集合,满足任意不在同一集合的点之间都有边相连。

      考虑对于原图:若x,y有连边,则x,y不一定不在同一个集合。

      若x,y无连边,则通过题意,x,y一定在同一个集合。

      故我们建出原图的补图(全集为完全图),这样就变成了找补图里的联通块个数。

      怎样建图?首先O(n^2)暴力建边肯定TLE,因此我们考虑维护集合S,每次扩展一个未加入补图联通块的点x进行BFS,从集合S中选出当前点x扩展不到的点进行入队,扩展到的点还原回S,这样便不用建图也可以找出联通块。

      code:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    #include<vector>
    using namespace std;
    const int N=100010;
    const int M=2000010;
    vector<int>S;
    vector<int>g[N];
    vector<int>ans;
    bool vis[N],h[N];
    int n,m;
    
    int bfs(int s)
    {
        int tot=0;
        queue<int>q;
        q.push(s);
        h[s]=1;//s被访问 
        while(q.size())
        {
            int u=q.front();q.pop();
            vis[u]=1;tot++;//联通块大小+1 
            for(int i=0;i<g[u].size();i++)h[g[u][i]]=1;//标记访问到的点 
            vector<int>tmp=S;S.clear();
            for(int i=0;i<tmp.size();i++)
            {
                if(h[tmp[i]])S.push_back(tmp[i]);//被访问的点不被拿出拓展其他点 
                else q.push(tmp[i]);//不被访问说明没有连边(即补图有边)入队 
            }
            for(int i=0;i<g[u].size();i++)
            h[g[u][i]]=0;//标记撤销 
        }return tot;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            g[x].push_back(y);
            g[y].push_back(x);
        }
        for(int i=1;i<=n;i++)S.push_back(i);//未扩展的节点 
        for(int i=1;i<=n;i++)
        if(!vis[i])ans.push_back(bfs(i));//bfs返回补图联通快大小 
        sort(ans.begin(),ans.end());
        cout<<ans.size()<<endl;
        for(int i=0;i<ans.size();i++)
        cout<<ans[i]<<" ";
    }
    •  图的连通性

      并查集缩点:

      LGOJ5492MST

      对于被删的边$i$,有两种情况。

      1、不在$mst$上,不做处理直接输出权值和

      2、在$mst$上,我们考虑把一条非树边加入$mst$中使其联通。

      删去的边$i$把$mst$分成两个集合$A,B$,我们要找的就是集合$A$和$B$之间所有连边中最小的。

      怎样找?我们把边从小到大排序,容易发现对于一条边$u->v$,若其满足条件,则路径$u->lca->v$会覆盖被删除的边。于是我们用书上带权并查集维护$minx$表示其上方的边被覆盖的最小权值,被更新过的点由于并查集的性质,再一次访问到的时候会直接跳过,这也就是并查集缩点。

    code:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=50010;
    const int M=100010;
    struct edge
    {
        int u,v,w,id;
    }e[M],fir[M];
    int cnt;
    int n,m,q;
    bool on_mst[M];
    int ans;
    int minw[N],dep[N],f[N];
    vector<int>g[N];
    
    struct DSU
    {
        int father[N];
        int find(int x){return x==father[x]?x:father[x]=find(father[x]);}
        void merge(int u,int v){int r1=find(u),r2=find(v);father[r1]=r2;}
        bool check(int u,int v){return find(u)==find(v);}
        void init(int x){for(int i=1;i<=x;i++)father[i]=i;}
    }dsu;
    
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    
    
    bool cmp(edge a,edge b)
    {
        return a.w<b.w;
    }
    
    void kruskal()
    {
        cnt=0;ans=0;
        dsu.init(n);
        sort(e+1,e+1+m,cmp);
        for(int i=1;i<=m;i++)
        {
            int u=e[i].u,v=e[i].v,w=e[i].w;
            int r1=dsu.find(u),r2=dsu.find(v);
            if(r1!=r2)
            {
                on_mst[e[i].id]=1;
                cnt++;ans+=w;
                dsu.father[r1]=dsu.father[r2];
                g[u].push_back(v);
                g[v].push_back(u);
            }
        }
    }
    
    void dfs(int u,int fa)
    {
        f[u]=fa;
        dep[u]=dep[fa]+1;
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if(v==fa)continue;
            dfs(v,u);
        }
    }
    
    void cover(int x,int y,int w)
    {
        x=dsu.find(x);y=dsu.find(y);
        while(x!=y)
        {
            if(dep[x]<dep[y])swap(x,y);
            dsu.merge(x,f[x]);
            minw[x]=w;
            x=dsu.find(f[x]);
        }
    }
        
    
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read(),z=read();
            fir[i].u=e[i].u=x;
            fir[i].v=e[i].v=y;
            fir[i].w=e[i].w=z;
            fir[i].id=e[i].id=i;
        }
        kruskal();
        if(cnt!=n-1)
        {
            q=read();
            while(q--)
            {
                cout<<"Not connected"<<endl;    
            }return 0;
        }
        dfs(1,0);
        memset(minw,-1,sizeof(minw));
        dsu.init(n);
        for(int i=1;i<=m;i++)
        {
            if(on_mst[e[i].id])continue;
            cover(e[i].u,e[i].v,e[i].w);
        }
        q=read();
        while(q--)
        {
            int id=read();
            if(!on_mst[id])printf("%d
    ",ans);
            else
            {
                int x=fir[id].u,y=fir[id].v,w=fir[id].w;
                if(dep[x]<dep[y])swap(x,y);
                if(minw[x]==-1)cout<<"Not connected"<<endl;
                else printf("%d
    ",ans-w+minw[x]);
            }
        }
    }

      $Tarjan$算法:

      1、点双联通分量

      2、边双联通分量

      P2860 [USACO06JAN]冗余路径Redundant Paths

      我们把无向图的环通过$Trajan$算法缩成点,这样原图就变成了一棵树。把叶子节点两两连接即可。

      

      3、强联通分量

    • 最短路

      1、$spfa$

      2、$dijkstra$

    • $MST$(最小生成树)

      1、$Boruvka$:

      $Boruvka$是一种和$Kruskal$不同的求解$MST$的方法,其思想是:
      对于每个点找出其连出的最小的边(这一步复杂度可以不和$m$相关)
      对这些边做一次$Kruskal$
      将同一个联通块的点缩起来,重复第一步,直到只剩一个点为止
      由环切性质,容易发现这个算法是正确的
      分析一下复杂度,选出来的边构成了一个基环树森林,每次$Kruskal$做完后剩下的点数就是基环树的个数,由于每个点都引出了一条边,于是个数最多的情况是若干个孤立的二元环,也就是说每次迭代点数至少减少一半,于是至多$logn$次就能结束算法。而每次$Kruskal$的复杂度是$O(n+m)$的(只用最开始排一次序),故总复杂度为O((n+m)logn)。

      2、$Kruskal$:

      

  • 相关阅读:
    云栖大会2020
    云栖大会2020
    云栖大会2020
    《利用知识图提高抽象摘要的事实正确性》
    onkeyup="this.value=this.value.replace(/[^1-9]D*$/,'')"
    javamail发送excel附件
    model.org.orgId
    window.parent.returnValue
    删除了用户的org和role
    window.top.popForm()表单以窗口的形式弹出
  • 原文地址:https://www.cnblogs.com/THRANDUil/p/11279893.html
Copyright © 2020-2023  润新知