图论总结

  • 图论总结

  • 补图

  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\n",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\n",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$:

  

 

posted @ 2019-07-31 23:35  EPs1l0h  阅读(320)  评论(0编辑  收藏  举报