zoukankan      html  css  js  c++  java
  • poj2226-Muddy Fields二分匹配 最小顶点覆盖 好题

    题目

    给到一个矩阵,有些格子上是草,有些是水。需要用宽度为1,长度任意的若干块木板覆盖所有的水,并不能覆盖草,木板可以交叉,但只能横竖放置,问最少要多少块板。

    分析

    经典的矩阵二分图构图和最小点覆盖。
    无非就是两种方向,横向和竖向。我们把水块连续的编成同一号,那么对于一个点,它会有一个横向编号和纵向编号。我们要覆盖这个点,只需要覆盖这条边即可。于是问题转化成了一个最小点覆盖问题,即在二分图上选出最少的点,使它们能够覆盖所有的边。这里引出König定理。

    König 定理

    二分图中,最小点覆盖=最大匹配.

    是不是感觉跟"最小割=最大流"有点像......
    可参看最大最小定理

    证明

    匈牙利算法的流程表明,一个最大匹配满足从任意一个未匹配点出发,都无法找到一条增广路径。假设最大匹配数为MM.
    对于右边的所有未匹配点,我们寻找它们的所有交替路(未匹配——匹配交替出现),并把这个交替路上(包括自己)的所有点打上标记,那么最小点覆盖的点集为:左边有标记的点+右边无打标记的点,点集大小为MM.
    接下来有三个问题:

    • 为什么点集大小为MM?
    • 为什么这个点集可以覆盖所有边?
    • 为什么这个点集是最小的?

    下面将按顺序证明。

    • 上述点集为中每个点都是一条匹配边的某一个顶点,所以点集大小与匹配边数相等。按照上述画法,若右边一个点没有匹配过,那么它会被打上标记;若左边一个点没有匹配过,那么走不到这个点,否则将是一条新增广路。因此右边无标记点在匹配边上,左边有标记点在匹配边上。又因为不可能出现一条匹配边右边无标记而左边有标记(这种情况下左端点可以通过匹配边走到右端点给它打上标记),所以这种计算方法不会算重复,故每个点可以对应一条匹配边,点集大小为匹配边数M
    • 不存在一条边,它的左边无标记,右边有标记。一条边可以被覆盖当且仅当左右其中一个端点在点集中。只要证明不存在一条边的左右端点均不在点集中即可,即证明不存在一条边的左端点无标记,右端点有标记。若这条边是匹配边,那么右端点不可能作为上述交替路的起点,所以标记是从左端点来到,故左端点会有标记;若这条边不是匹配边,那么右端点肯定会被选作交替路起点,从而得到标记。所以不存在一条边,它的左边无标记,右边有标记。
    • 一个点覆盖必须覆盖所有的匹配边,而匹配边的数量为M,没有更小的情况。由于一个点不可能连出两条匹配边(不符合匹配的定义),所以要覆盖M条匹配边,需要至少M个点。我们已经构造出了这种方案。

    综上,最小点覆盖数=最大匹配数。

    这类题的建图方法:

    把矩阵作为一个二分图,以行列分别作为2个顶点集

    首先以每一行来看,把这一行里面连续的*编号,作为一个顶点

    再以每一列来看,把这一列里面连续的*编号,作为一个顶点

    则每一个*都有2个编号,以行看时有一个,以列看时有一个,则把这2个编号连边,容量为1

    再建一个源点,连接所有行的编号,一个汇点,连接所有列的编号

    这道题要求的是,所有*都被覆盖,即找到一个顶点的集合S,使得任意边都有至少一个顶点属于

    S,即求一个点集顶点覆盖S,又要木板数最少,所以求的就是最小顶点覆盖。

    最小顶点覆盖怎么求?

    二分图中,有:

    最小顶点覆盖=最大匹配

    所以这道题就转化为求二分图的最大匹配了

    再转化为最大流dinic算法。

    dinic算法

    #include<cstdio>
    #include<cstring>
    #include<vector>
    #include<queue>
    
    using namespace std;
    
    const int maxn=2510;
    const int inf=0x3f3f3f3f;
    const int s=0;
    int t;
    int tota;
    int totb;
    
    inline int min(int x,int y)
    {
        return x<y?x:y;
    }
    
    struct Edge
    {
        int to,cap,rev;
    };
    vector<Edge>edge[maxn];
    int iter[maxn];
    int level[maxn];
    char str[55][55];
    int hash[55][55];
    
    void addedge(int from,int to,int cap)
    {
        edge[from].push_back((Edge){to,cap,edge[to].size()});
        edge[to].push_back((Edge){from,0,edge[from].size()-1});
    }
    
    void build_graph(int n,int m)
    {
        for(int i=0;i<n*m;i++)
            edge[i].clear();
        tota=0;
        for(int i=1;i<=n;i++)
        {
            int j=1;
            while(j<=m)
            {
                if(str[i][j]=='*')
                {
                    tota++;
                    hash[i][j]=tota;
                    while(j<=m&&str[i][j+1]=='*')
                    {
                        j++;
                        hash[i][j]=tota;
                    }
                }
                j++;
            }
        }
        totb=tota;
        for(int j=1;j<=m;j++)
        {
            int i=1;
            while(i<=n)
            {
                if(str[i][j]=='*')
                {
                    totb++;
                    addedge(hash[i][j],totb,1);
                    while(i<=n&&str[i+1][j]=='*')
                    {
                        i++;
                        addedge(hash[i][j],totb,1);
                    }
                }
                i++;
            }
        }
        t=tota+totb+1;
        for(int i=1;i<=tota;i++)
            addedge(s,i,1);
        for(int i=tota+1;i<=totb;i++)
            addedge(i,t,1);
    }
    
    void bfs()
    {
        memset(level,-1,sizeof level);
        queue<int>que;
        while(!que.empty())
            que.pop();
        que.push(s);
        level[s]=1;
        while(!que.empty())
        {
            int u=que.front();
            que.pop();
            for(int i=0;i<edge[u].size();i++)
            {
                Edge &e=edge[u][i];
                if(e.cap>0&&level[e.to]<0)
                {
                    level[e.to]=level[u]+1;
                    que.push(e.to);
                }
            }
        }
    }
    
    int dfs(int u,int f)
    {
        if(u==t)
            return f;
        for(int &i=iter[u];i<edge[u].size();i++)
        {
            Edge &e=edge[u][i];
            if(e.cap>0&&level[e.to]>level[u])
            {
                int d=dfs(e.to,min(f,e.cap));
                if(d>0)
                {
                    e.cap-=d;
                    edge[e.to][e.rev].cap+=d;
                    return d;
                }
    
            }
        }
        return 0;
    }
    
    int solve()
    {
        int flow=0;
        while(true)
        {
            bfs();
            if(level[t]<0)
                return flow;
            memset(iter,0,sizeof iter);
            int f;
            while(f=dfs(s,inf))
            {
                flow+=f;
            }
        }
    }
    
    int main()
    {
        int n,m;
        while(~scanf("%d%d",&n,&m))
        {
            for(int i=1;i<=n;i++)
            {
                scanf("%s",str[i]+1);
            }
            build_graph(n,m);
            printf("%d
    ",solve());
        }
        return 0;
    }
    View Code

    匈牙利算法

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    const int maxn=55;
    const int maxc=1e3+10;
    bool f[maxc][maxc],alr[maxc];
    int match[maxc],row[maxn][maxn],col[maxn][maxn],lid=0,rid=0,n,m,ans=0;
    char s[maxn][maxn];
    bool dfs(int x) {
        for (int i=1;i<=rid;++i) if (!alr[i] && f[x][i]) {
            alr[i]=true;
            if (!match[i] || dfs(match[i])) {
                match[i]=x;
                return true;
            }
        }
        return false;
    }
    int main() {
    
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;++i) scanf("%s",s[i]+1);
        for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) if (s[i][j]=='*') {
            ++lid;
            while (j<=m && s[i][j]=='*') row[i][j++]=lid;
            --j;
        }
        for (int j=1;j<=m;++j) for (int i=1;i<=n;++i) if (s[i][j]=='*') {
            ++rid;
            while (i<=n && s[i][j]=='*') col[i++][j]=rid;
            --i;
        }
        for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) if (s[i][j]=='*') f[row[i][j]][col[i][j]]=true;
        // for(int i = 1;i <= n; i ++)for(int j = 1;j <= m;j ++){
        //     cout << f[row[i][j]][col[i][j]] << " " ;
        // }
        for (int i=1;i<=lid;++i) memset(alr,0,sizeof alr),ans+=dfs(i);
        printf("%d
    ",ans);
    }
    View Code
  • 相关阅读:
    C# 文件类的操作---删除
    C#实现Zip压缩解压实例
    UVALIVE 2431 Binary Stirling Numbers
    UVA 10570 meeting with aliens
    UVA 306 Cipher
    UVA 10994 Simple Addition
    UVA 696 How Many Knights
    UVA 10205 Stack 'em Up
    UVA 11125 Arrange Some Marbles
    UVA 10912 Simple Minded Hashing
  • 原文地址:https://www.cnblogs.com/DWVictor/p/11348336.html
Copyright © 2011-2022 走看看