zoukankan      html  css  js  c++  java
  • 二分图 题目小结

    脑子都要废了最近,终于有空整理题目了,学了两天二分图,然后机房大佬讲,你二分图写的我网络流都能写,心神俱疲;

    我不想在这里过多介绍二分图,必将网上大佬博客都很多;

    二分图

    如果一张无向图的 n(n2) 个节点可以分成 A,BA,B 两个非空集合,

    其中 AB 为空,并且在同一集合内的点之间都没有边相连,那么称这张无向图为一张二分图。 A,B 分别称为二分图的左部和右部。

    二分图判定定理

    无向图是二分图⇔图中无奇环(长度为奇数的环)。

    常用方法dfs染色法,判断一张图是否为二分图;

    有1表示黑色,2表示白色;

    inline bool dfs(int x,int color) {
        v[x]=color;
        for(int i=0;i<edge[x].size();i++) {
            int y=edge[x][i].first;
            if(v[y]==color) return 0;
            if(!v[y]&&!dfs(y,3-color)) return 0; 
        }
        return 1;
    }

    这里想总结一下算法进阶的例题,写一下自己的理解;

    1.关押罪犯  洛谷

    最开始使用并差集写的,以前写过一篇并查集的博客,这里我们考虑用二分图的做法写这道题目;

    最大的怒气值最小,考虑一个判定问题,市长能否在一种方案分配下,看到怒气值最大为mid;显然当对于较小的mid成立时,较大的mid也成立,所以答案具有可二分性;

    所以我们转化为一个判定问题;

    二分答案,我们假设当前怒气值为mid,那么我们将大于mid的怒气值的罪犯(当作节点)连边,如果建出来一张二分图,答案显然合理,令r=mid即可;

    二分图code;

    #include<bits/stdc++.h>
    using namespace std;
    #define N 500100 
    #define pii pair<int,int>
    int n,m,maxn,v[N];
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
        x*=f;
    }
    
    struct gg {
        int x,y,v;
    }a[N<<1];
    
    vector<pii> edge[N];
    
    inline bool mycmp(gg x,gg y) {
        return x.v>y.v;
    }
    
    inline bool dfs(int x,int color) {
        v[x]=color;
        for(int i=0;i<edge[x].size();i++) {
            int y=edge[x][i].first;
            if(v[y]==color) return 0;
            if(!v[y]&&!dfs(y,3-color)) return 0; 
        }
        return 1;
    }
    
    inline bool check(int mid) {
        for(int i=1;i<=n;i++) edge[i].clear();
        for(int i=1;i<=m;i++) {
            if(a[i].v<=mid) break;
            edge[a[i].x].push_back(make_pair(a[i].y,a[i].v));
            edge[a[i].y].push_back(make_pair(a[i].x,a[i].v));
        }
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;i++) {
            if(!v[i]&&!dfs(i,1)) return false;
        }
        return true;
    }
    
    int main() {
        
        read(n); read(m);
        for(int i=1;i<=m;i++) {
            read(a[i].x); read(a[i].y); read(a[i].v);
            maxn+=a[i].v;
        }
        sort(a+1,a+m+1,mycmp);
        int l=0,r=maxn;
        while(l<r) {
            int mid=(l+r)>>1;
            if(check(mid)) r=mid;
            else l=mid+1;
        } 
        cout<<l<<endl;
        return 0;
    }
    View Code

    并差集code1;

    #include<bits/stdc++.h>
    using namespace std;
    #define N 500001
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
        x*=f;
    }
    int n,m,d[N],f[N];
    struct pink
    {
        int x,y,v;
    }a[N<<1];
    bool mycmp(pink x,pink y)
    {
        return x.v>y.v;
    }
    int find(int x)
    {
        return f[x]==x?x:f[x]=find(f[x]);
    }
    int main()
    {
        int flag=0;
        read(n);read(m);
        for(int i=1;i<=m;i++)
        {
            read(a[i].x);read(a[i].y);read(a[i].v);
        }
        for(int i=1;i<=2*n;i++)    f[i]=i;
        sort(a+1,a+m+1,mycmp);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            x=a[i].x,y=a[i].y;
            int xx=find(a[i].x);
            int yy=find(a[i].y);
            if(xx==yy)
            {
                cout<<a[i].v<<endl;
                return 0;
            }
            f[yy]=find(a[i].x+n);
            f[xx]=find(a[i].y+n);
        }
        puts("0");
        return 0;
    }
    View Code

    并差集code2;

    #include<bits/stdc++.h>
    using namespace std;
    #define N 500001
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
        x*=f;
    }
    int n,m,d[N],f[N];
    struct pink
    {
        int x,y,v;
    }a[N<<1];
    bool mycmp(pink x,pink y)
    {
        return x.v>y.v;
    }
    int find(int x)
    {
        return f[x]==x?x:f[x]=find(f[x]);
    }
    int main()
    {
        int flag=0;
        read(n);read(m);
        for(int i=1;i<=m;i++)
        {
            read(a[i].x);read(a[i].y);read(a[i].v);
        }
        for(int i=1;i<=n;i++)
            f[i]=i;
        sort(a+1,a+m+1,mycmp);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            x=a[i].x,y=a[i].y;
             if(find(x)==find(y))
            {
                printf("%d
    ",a[i].v);
                flag=1;
                break;
            }
            else
            {
                if(!d[x]) d[x]=y;
                else
                {
                    int p=find(d[x]);
                    f[p]=find(y);
                }
                if(!d[y]) d[y]=x;
                else
                {
                    int q=find(d[y]);
                    f[q]=find(x);
                }
            }
        }
        if(!flag) printf("%d
    ",0);
        return 0;
    }
    View Code

    二分图最大匹配

    图的匹配

    任意两条边都没有公共端点的边的集合被称为图的一组匹配。

    二分图最大匹配

    在二分图中,包含边数最多的一组匹配被称为二分图的最大匹配。

    其他相关定义

    对于任意一组匹配 S(边集),属于 S 的边被称为匹配边,不属于 S 的边被称为非匹配边。

    匹配边的端点被称为匹配点,其他节点被称为非匹配点。

    如果二分图中存在一条连接两个非匹配点的路径 path ,使得非匹配边与匹配边在 path 上交替出现,那么称 path是匹配 S 的增广路(也称交错路)。

    增广路的性质

    1. 长度为奇数
    2. 奇数边是非匹配边,偶数边是匹配边。
    3. 如果把路径上所有边的状态(是否为匹配边)取反,那么得到的新的边集 S' 仍然是一组匹配,并且匹配的边数增加了 1 。

    结论

    二分图的一组匹配 S 是最大匹配⇔图中不存在 S 的增广路。

    匈牙利算法(增广路算法)

    主要过程

    1. 设 SS 为空集,即所有边都是非匹配边。
    2. 寻找增广路 path ,把 path 上所有边的匹配状态取反,得到一个更大的匹配 S' 。
    3. 重复第 22 步,直至图中不存在增广路。

    寻找增广路

    依次尝试给每一个左部节点 x 寻找一个匹配的右部节点 y 。

    y 与 x 匹配需满足下面两个条件之一:

    1. y 是非匹配点。
    2. y 已与 x' 匹配,但从 x' 出发能找到另一个 y' 与之匹配。

    时间复杂度

    O(nm);

    1.棋盘覆盖

    对于1*2的骨牌放置在N*M的矩阵中的方案数,我们可以用状压DP来写,

    但对于这道题,有些各自有限制,并且数据范围限制,无法用状压写;

    所以我们为什么考虑到二分图匹配的呢;

    对于二分图匹配,他一定具有一个两个性质,我们成为0元素,1元素;

    0元素:节点分为两个集合,集合内部0条边;

    1要素:每个节点只能和一条匹配边相连;

    映射到这个题目;

    1元素对应,一个各自只能被一个骨牌覆盖,覆盖两个相邻的格子,那么假设这些格子没有被限制,我们将其连边,我们将棋盘进行黑白染色,我们显然发现相同颜色无法连边,对应0元素;

    所以黑色格子为一个集合,白色格子为一个集合;

    让骨牌不重叠放置最多,是一个二分图最大匹配问题;

    #include<bits/stdc++.h>
    using namespace std;
    #define N 20010
    struct gg {
        int x,y,next;
    }a[N<<1];
    
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
        x*=f;
    }
    
    
    int n,m,tot,x,y,b[210][210],v[N],match[N<<1],lin[N];
    
    inline void add(int x,int y) {
        a[++tot].y=y;
        a[tot].next=lin[x];
        lin[x]=tot;
    }
    
    inline bool dfs(int x) {
        for(int i=lin[x];i;i=a[i].next) {
            int y=a[i].y;
            if(!v[y]) {
                v[y]=1;
                if(!match[y]||dfs(match[y])) {
                    match[y]=x; match[x]=y; return 1;
                }
            }
        }
        return 0;
    }
    
    int main() {
        read(n); read(m);
        for(int i=1;i<=m;i++) {
            read(x); read(y);
            b[x][y]=1;
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++) {
                if(b[i][j]) continue;
                int id=(i-1)*n+j;//记录是第几个格子; 
                if(!b[i][j-1]&&j>1) {
                    add(id,id-1);//放置一个横着的骨牌; 
                }
                if(!b[i][j+1]&&j<n) {
                    add(id,id+1);
                }
                if(!b[i-1][j]&&i>1) {
                    add(id,id-n);
                }
                if(!b[i+1][j]&&i<n) {
                    add(id,id+n);
                }
            }
        int ans=0;
        for(int i=1;i<=n*n;i++){
            if(!match[i]) {
                if(dfs(i)==1) ans++;
                memset(v,0,sizeof(v));
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    View Code

    2.车的放置

    类比刚才那道题,以及是有限制,我们尝试寻找一个01要素;

    1要素:每行每列只能放1辆车,某个格子(i,j)连边,说明第i行第j列占据一个名额;

    0要素:每个车不能即在第i行又在第j行,所以两个行对应没有连边;

    求二分图最大匹配即可;

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,T,tot,x,y,a[201][201],match[400010],lin[100010],v[100010];
    
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
        x*=f;
    }
    
    struct gg {
        int y,next;
    }e[400010];
    
    inline void add(int x,int y) {
        e[++tot].y=y;
        e[tot].next=lin[x];
        lin[x]=tot;
    }
    
    inline bool dfs(int x) {
        for(int i=lin[x];i;i=e[i].next) {
            int y=e[i].y;
            if(!v[y]) {
                v[y]=1;
                if(!match[y]||dfs(match[y])) {
                    match[y]=x; match[x]=y; return 1;
                }
            }
        }
        return 0;
    }
    
    int main() {
        read(n); read(m); read(T);
        for(int i=1;i<=T;i++) {
            read(x); read(y);
            a[x][y]=1;
        }
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if(a[i][j]) continue;
                add(i,j+n);
            }
        }
        int ans=0;
        for(int i=1;i<=n;i++) {
            if(!match[i]) {
                if(dfs(i)) ans++;
                memset(v,0,sizeof(v));
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    View Code

    二分图最小点覆盖

    给定一张二分图,求出一个最小的点集 S,使得图中任意一条边都已至少一个端点属于 S 。这个问题被称为二分图的最小点覆盖,简称最小覆盖。

    定理

    二分图最小点覆盖包含的点数 == 二分图最大匹配包含的边数。

    Machine Schedule

    在二分图最大匹配中,我们寻找的时01要素,而在二分图最小点覆盖时,我们寻找的时2要素;

    即每条边有两个端点,两者至少选择一个;

    而在这道题中,每个任务要么在A中完成,要么在B中完成,二者必选其一,因为我们将A的M中模式作为左部点,将B的M种模式作为右部点,

    将每个任务作为边连接a[i]和b[i],那么显然最少启动次数就是二分图最小匹配;

    时间复杂度O(NM);

    #include<bits/stdc++.h>
    using namespace std;
    int n,m,k;
    const int maxx=1000001;
    struct node{
        int y;
        int next;
    }e[maxx];
    int kk,lin[maxx],v[20002];
    int match[maxx]; 
    void add(int u,int v) {
        e[++kk].y=v;
        e[kk].next=lin[u];
        lin[u]=kk;
    }
    int dfs(int u) {
        for(int i=lin[u];i;i=e[i].next) {
            int y=e[i].y;
            if(!v[y]) {
                v[y]=1;
                if(!match[y]||dfs(match[y])) {
                    match[y]=u; match[u]=y;
                    return 1;
                }
            }
        }
        return 0;
    }
    int main() { 
        while(1) {
            memset(match,0,sizeof(match));
            memset(lin,0,sizeof(lin));
            int sum=0;
            scanf("%d",&n);
            if(n==0) break;
            scanf("%d%d",&m,&k);
            for(int i=1;i<=k;i++) {
                int x,y,z;
                scanf("%d%d%d",&x,&y,&z);
                if(y==0||z==0) continue;
                else  add(y,z+n);
            }
            for(int i=1;i<=n;i++) {
                memset(v,0,sizeof(v));
                if(dfs(i)) sum++;
            }
            printf("%d
    ",sum);
        }
    }
    View Code

    POJ2226 Muddy Fields

    泥泞的区域

    每块泥地要么被一块横着的木板覆盖,要么被一块竖着的木板覆盖,二者至少选择一个;

    因为木板不能覆盖干净的地面,所以我们处理出来行泥泞块和列泥泞块,作为二分图的左右部点;

    求出二分图最小点覆盖;

    #include<bits/stdc++.h>
    using namespace std;
    #define N 1100 
    int v[N],match[N],lin[N];
    int n,m,k,cnt,tot1,tot2;
    char Map[60][60];
    int a[1100][1100],b[1100][1100];
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
        x*=f;
    }
    
    struct gg {
        int y,next;
    }e[400010];
    
    inline void add(int x,int y) {
        e[++cnt].y=y;
        e[cnt].next=lin[x];
        lin[x]=cnt;
    }
    
    inline bool dfs(int x) {
        for(int i=lin[x];i;i=e[i].next) {
            int y=e[i].y;
            if(!v[y]) {
                v[y]=1;
                if(!match[y]||dfs(match[y])) {
                    match[y]=x;
                    return 1;
                }
            }
        }
        return 0;
    }
    
    int main() {
        read(n); read(m);
        for(int i=1;i<=n;i++) 
            for(int j=1;j<=m;j++) {
                cin>>Map[i][j];
            }
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if(Map[i][j]=='*') {
                    if(Map[i][j-1]=='*') {
                        a[i][j]=a[i][j-1];
                    }
                    else  a[i][j]=++tot1;
                }
            }
        }//横着放;
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if(Map[i][j]=='*') {
                    if(Map[i-1][j]=='*') {
                        b[i][j]=b[i-1][j]; 
                    }
                    else {
                        b[i][j]=++tot2;
                    }
                }
            }
        } 
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                if(Map[i][j]=='*') {
                    add(a[i][j],b[i][j]);
                }
            }
        }
        int ans=0;
        for(int i=1;i<=tot1;i++) {
            memset(v,0,sizeof(v));
            if(dfs(i)) ans++;
        }
        cout<<ans<<endl;
        return 0;
    }
    View Code

    二分图最大独立集

    图的独立集

    在一张无向图中,满足任意两点之间都没有边相连的点集被称为图的独立集。包含点数最多的一个被称为图的最大独立集。

    图的团

    在一张无向图中,满足任意两点之间都有边相连的子图被称为图的团。包含点数最多的一个被称为图的最大团。

    定理

    1. 无向图 G 的最大团 == 补图 G' 的最大独立集。
    2. 对于一般无向图,最大团、最大独立集是 NPC 问题。
    3. 设 G 是有 n个节点的二分图, G 的最大独立集大小 =n- 最小点覆盖数 =n-最大匹配数。

    P3355 骑士共存问题

    ACWing(两道题目输入不一样);

    与棋盘覆盖类似;

    黑白相间染色棋盘,把黑、白色格子分别作为左、右部节点。若两个格子是“日”字对角,则在对应的节点之间连边。“日”字对角的两个节点显然颜色一定不同。

    求上述二分图的最大独立集即可。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 10010
    
    template<typename T>inline void read(T &x)
    {
        x=0;T f=1,ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')  f=-1;  ch=getchar();}
        while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);  ch=getchar();}
        x*=f;
    }
    
    const int dx[8]={-1,-2,-2,-1,1,2,2,1};
    const int dy[8]={-2,-1,1,2,2,1,-1,-2};
    
    int n,m,t,ans,tot,x,y,match[N],lin[N],v[N];
    int Map[110][110];
    vector<int> e;
    
    struct gg {
        int x,y,next;
    }a[100*100<<2];
    
    inline void add(int x,int y) {
        a[++tot].y=y;
        a[tot].next=lin[x];
        lin[x]=tot;
    }
    
    inline bool dfs(int x) {
        for(int i=lin[x];i;i=a[i].next) {
            int y=a[i].y;
            if(!v[y]) {
                v[y]=1;
                if(!match[y]||dfs(match[y]))  {
                    match[y]=x; return 1;
                }
            }
        }
        return 0;
    }
    
    int main() {
        read(n); read(m); read(t);
        for(int i=1;i<=t;i++) {
            read(x); read(y);
            Map[x][y]=1;
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if(!Map[i][j]&&(i+j)%2)
                {
                    e.push_back(m*(i-1)+j);
                    for(int k=0;k<8;k++)
                    {
                        int x=i+dx[k],y=j+dy[k];
                        if(x<1||x>n||y<1||y>m) continue;
                        if(!Map[x][y])
                            add(m*(i-1)+j,m*(x-1)+y);
                    }   
                }
        for(int i=0;i<e.size();i++) {
            memset(v,0,sizeof(v));
            if(dfs(e[i])) ans++;
        } 
        cout<<n*m-t-ans<<endl;
        return 0;
    }
    View Code
  • 相关阅读:
    在报表中录入数据时如何实现行列转换
    CNN卷积神经网络代码实现【基于Python,Tensorflow】
    Spark Word2Vec算法代码实现
    Spark ML逻辑回归
    SolrCloud搜索引擎集群搭建【伪分布式、完全分布式】
    Scala之List,Set及Map基本操作
    bs4爬虫入门
    Scrapy爬虫入门
    Solr参数详解【Web客户端,DIH数据导入】
    Python多态
  • 原文地址:https://www.cnblogs.com/Tyouchie/p/11105495.html
Copyright © 2011-2022 走看看