两极相通——浅析最大—最小定理在信息学竞赛中的应用

目录

简介

1:Konig定理

最小顶点覆盖:

最大匹配数:

Konig定理的证明

2:最大流-最小割定理

3:实战(HDU3870)

题意

题目分析

代码

 


简介

本文涵盖了周东《两极相通——浅析最大—最小定理在信息学竞赛中的应用》的大部分内容,文章脉络基本相同,但是加上了我自己对这篇文章的理解。书写本文的初心是自己在学习最小割与最短路的转换问题的时候无意之间发现了这篇文章,想把这篇文章让更多人知道,故写下本文。

1:Konig定理

主要内容在任意一个二部图G中,最大匹配数ρ(G)=最小覆盖数c(G).

 

最小顶点覆盖

定义:假如选了一个点就相当于覆盖了以它为端点的所有边。最小顶点覆盖就是选择最少的点来覆盖所有的边

如果没有理解定义,那么我先说一下什么是顶点覆盖

                                                                

当我们选择绿色圈所圈住的顶点的时候,与这个点关联的三个边都被覆盖了.

取出一些点,使得这个图里面的所有的边都被覆盖,当取得的顶点的个数最少的时候,这就是一个最小定点覆盖。

下面试非二分图下的最小顶点覆盖问题.

                         

上图中红色圆圈就是我们要求的最小顶点的数量,这两个顶点刚好可以覆盖每一条边,并且数量是最少的。

最大匹配数

定义:在一个无向图中,定义一条边覆盖的点为这条边的两个端点。找到一个边集S包含最多的边,使得这个边集覆盖到的所有顶点中的每个顶点只被一条边覆盖。S的大小叫做图的最大匹配。

                                                                           

上图中的最大匹配数量是3(红色边的数量)

Konig定理的证明

1:最大匹配数不超过最小覆盖数.

2:任取一个最小覆盖Q,一定可以构造出一个与之大小相等的匹配M.

 

个人参考博客认为另一个较好的证明:

                                                   

首先,我们找到一个二分图的最大匹配(左图中的蓝色边),对于二分图的左半部分我们假设其为A集合,相应的右半部分我们假设其为B集合,从B集合开始,遵循“非匹配边——>匹配边.......——>匹配边”(注意是匹配边结束),沿途标记所有的经过的点.最后我们得到右图这个样子的关系,对于A集合和B集合,我们选取A集合中的标记的点和B集合中的未被标记的点,记为一个集合S,那么点集S就是一个最小点覆盖。

其次,我们现在要做的是通过反例去推翻这个观点,也就是说如果我们能找到这样一条边,这条边的两个端点分别是A集合中的未标记的点和B集合中标记的点,仔细想一下,对于一个匹配的边,一定是从A集合出发到B集合的,这样子的话,对于一个匹配边,它的左右两个端点都应该是标记点!(S中的点能够覆盖图中的所有边)

最后,每个点其实都是匹配最大匹配集合中某一条边的一个端点,因为队友B集合来说,如果说B集合中没有标记的点没有对应某一个匹配的边的话,不就是非匹配边嘛,这样这个点就应该被标记!因为我们遵循“非匹配边——>匹配边.......——>匹配边”的原则,那么那么A集合中标记的点在B集合中一定有对应的点!又因为一条匹配边,其在A和B两个集合中要么都被标记,要么都没有被标记,这就使得S集合中点的个数就是最大匹配数。

 

2:最大流-最小割定理

主要内容:

1:最大流的流量不超过最小割的容量.

2:存在一个流x和一个割c,并且x的流量等于c的容量.

例题:

一个牧场由R*C个格子组成

牧场内有N条干草运输通道,每条连接两个水平或垂直相邻的方格,最大运输量为Li.

(1,1)内有很多干草, Farmer John希望将最多的干草运送到(R,C)内

最大运输量

当我们在实际解决这样的问题的时候,首先需要考虑的是数据范围,现在如果说在下图中,R=C=3的话,最大的运输量就是7.

那么我们直接跑最大流就行了,EK算法时间复杂度为O(n*m^2),Dinic和SAP算法的时间复杂度为O(n^2*m)(其中n是顶点的数量,m是边的数量),在数据范围较小的情况下可以直接以图中的交点为点,以两点之间的连线为边,但是这个是一个矩形,我们在求网络流的时候计算点的数量的时候求得最大的点数为200*200=40000,最大的边数为80000,那么即便是R=C=200也会TLE.

于是乎,这道题的最大流-最小割转换就出来了~~~~~

1:题目给出的是一个平面图

对于平面图来说:

1:如果一个联通的平面图内有n个点,m条边和f个面,那么满足关系f=m-n+2.(可以拿一个三角形有两个面进行快速回忆).

2:每一个平面图G都有一个与之对偶的平面图G*

           ①:G*中的每个点对应G中的一个面(关于这一点,注意下面的构图)

                  

           ②:对于G的每条边e,如果e属于两个面f1和f2,那么加入边(f1*,f2*),如果e只属于一个面f,那么加入回边(f*,f*)

                       

(我认为3*经过45到4*的边不能经过56,于是就在原图的基础上进行了一个修改)

一条从s*到t*的路径,对应的就是一个s-t的割,那么如果说每一条边的长度都等于其容量的话,那么最小割的容量就是最短路的长度。

此时使用堆优化Dijkstra的时间复杂度为O(nlogn).

 

3:实战(HDU3870)

题意

给定一个n行n列的矩形图,有一些小偷在A城市要去B城市偷东西,警察为了阻止他们在小偷经过的边上部署警察,每个边部署警察的保护费都不相同,问最少需要多少的保护费?

题目分析

可以发现,小偷是从左上角向右下角出发的,那么我们可以构建网络流,但是数据范围过大,所以直接跑网络流的话会TLE,这个时候我们可以构建对偶图,将问题进行一个简单的转化,我们想把s和t切断,那么就是想找到一个最短路,这个最短路的目的就是切割的作用,又因为路径的长度是根据容量来计算的,所以最短路就是最小割。

代码

最开始使用vector疯狂爆内存,后来改成邻接表过的,我把vector部分注释掉方便大家查看。

#include<algorithm>
#include<map>
#include<iostream>
#include<queue>
#include<vector>
#include<set>
#include<string>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=405;
const int maxm=160005;
const int inf=0x3f3f3f3f;


struct qnode{
    int v,c;
    qnode(int _v=0,int _c=0):v(_v),c(_c){}
    bool operator <(const qnode &r) const{
        return c>r.c;
    }
};

struct Edge{
    int v,cost,next;
    Edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};

int head[maxm],l;
//vector<Edge>E[maxm*4];
Edge E[maxm*4];
bool vis[maxm];
int dist[maxm];

void Dijkstra(int n,int start)
{
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;++i) dist[i]=inf;
    priority_queue<qnode>que;
    while(!que.empty())que.pop();
    dist[start]=0;
    que.push(qnode(start,0));
    qnode tmp;
    while(!que.empty())
    {
        tmp=que.top();
        que.pop();
        int u=tmp.v;
        if(vis[u])continue;
        vis[u]=true;
        for(int i=head[u];i!=-1;i=E[i].next){
            int v=E[i].v;
            int cost=E[i].cost;
            if(!vis[v]&&dist[v]>dist[u]+cost){
                dist[v]=dist[u]+cost;
                que.push(qnode(v,dist[v]));
            }

        }
//        for(int i=0;i<E[u].size();++i)
//        {
//            int v=E[tmp.v][i].v;
//            int cost=E[u][i].cost;
//            if(!vis[v]&&dist[v]>dist[u]+cost)
//            {
//                dist[v]=dist[u]+cost;
//                que.push(qnode(v,dist[v]));
//            }
//        }
    }
}

void addedge(int u,int v,int w){
//    E[u].push_back(Edge(v,w));
    E[l].v=v;
    E[l].cost=w;
    E[l].next=head[u];
    head[u]=l++;
}

int main()
{
    int T,n;
    for(cin>>T;T;T--){
        cin>>n;
        memset(head,-1,sizeof(head)),l=0;
        for(int i=1;i<=n;++i){
            for(int j=1;j<=n;++j){
                    int value;
                scanf("%d",&value);

                if(i==1&&j!=n){
                    addedge(maxm-1,(i-1)*n+j,value);
                }
                if(j==n&&i!=n){
                    addedge(maxm-1,(i-1)*n+j-1,value);
                }
                if(j==1&&i!=n) {
                    addedge( (i-1)*n+j,maxm-2,value );
                }

                if(i==n&&j!=n){
                    addedge( (i-2)*n+j,maxm-2,value );
                }
                if(i!=n&&j!=n){
                    if(i>1){
                        addedge((i-1)*n+j,(i-2)*n+j,value);
                        addedge((i-2)*n+j,(i-1)*n+j,value);
                    }
                    if(j>1){
                        addedge((i-1)*n+j,(i-1)*n+j-1,value);
                        addedge((i-1)*n+j-1,(i-1)*n+j,value);
                    }
                }
            }
        }
        Dijkstra(maxm,maxm-1);
        printf("%d\n",dist[maxm-2]);
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值