zoukankan      html  css  js  c++  java
  • 图论:KM算法

    如果,将求二分图的最大匹配的所有匹配边的权重看做1

    那么用匈牙利算法求二分图的最大匹配的问题也可以看成求二分图的最大权匹配

    如果边权是特例,我们就要使用KM算法来做了

    这个算法其实还是比较难的,会用就不错了,更不要说证明了

    这里以HDU2255为例,这是一个裸题

    在这个题目里面X和Y的size是一样的

    然后我们稍微介绍一下这个算法(详细的以后再说吧,目前能力不够)

    int n,nx,ny,ans;
    int linker[maxn],lx[maxn],ly[maxn],slack[maxn],visx[maxn],visy[maxn];
    int G[maxn][maxn];

    linker记录的是与当前的下标节点(Y中)相连的X节点,lx和ly是节点顶标,slack是Y定点的松弛量函数

    邻接矩阵存储

    在这里面,如果有的边不存在,设置权重为0,这样图就可以近似看成一个全连接二分图

        for(int i=1;i<=nx;i++)
        {
            lx[i]=-INF;
            for(int j=1;j<=ny;j++)
            {
                if(G[i][j]>lx[i]) lx[i]=G[i][j];
            }
        }

    首先初始化X中节点的节点顶标

    就是对于每一个节点,看其所连接的所有的边,将最大权重设置为X节点顶标

    然后呢,就是从每个节点开始进行DFS增广

    根据情况修改可行顶标

        for(int x=1;x<=nx;x++)
        {
            for(int i=1;i<=ny;i++) slack[i]=INF;
            while(1)
            {
                memset(visx,0,sizeof(visx));
                memset(visy,0,sizeof(visy));
                if(dfs(x))    break;  //找到增广路,进入下一个点的增广 
            //如果失败,需要改变顶标使图中可行边数量增加
            //在所有的增广路的x顶标中减去常数d
            //在所有增广路的Y顶标中增加一个常数d
                 int d=INF;
                 for(int i=1;i<=ny;i++)
                     if(!visy[i]&&d>slack[i])
                         d=slack[i];
                 for(int i=1;i<=nx;i++)
                     if(visx[i]) lx[i]-=d;
                 for(int i=1;i<=ny;i++)
                     if(visy[i]) ly[i]+=d;
                     else slack[i]-=d;
            }
        }

    然后DFS增广部分如下:

    int dfs(int x)
    {
        visx[x]=1;
        for(int y=1;y<=ny;y++)
        {
            if(visy[y]) continue;
            int tmp=lx[x]+ly[y]-G[x][y];
            if(tmp==0)
            {
                visy[y]=1;
                if(linker[y]==-1||dfs(linker[y]))
                    {linker[y]=x;return 1;}
            }
            else if(slack[y]>tmp) slack[y]=tmp;
        }
        return 0;
    }

    具体原理先鸽了,以后再说

    然后给出完整的实现:

     1 #include<cstdio>
     2 #include<cstring>
     3 using namespace std;
     4 const int INF=1000000000;
     5 const int maxn=305;
     6 int n,nx,ny,ans;
     7 int linker[maxn],lx[maxn],ly[maxn],slack[maxn],visx[maxn],visy[maxn];
     8 int G[maxn][maxn];
     9 int dfs(int x)
    10 {
    11     visx[x]=1;
    12     for(int y=1;y<=ny;y++)
    13     {
    14         if(visy[y]) continue;
    15         int tmp=lx[x]+ly[y]-G[x][y];
    16         if(tmp==0)
    17         {
    18             visy[y]=1;
    19             if(linker[y]==-1||dfs(linker[y]))
    20                 {linker[y]=x;return 1;}
    21         }
    22         else if(slack[y]>tmp) slack[y]=tmp;
    23     }
    24     return 0;
    25 }
    26 int KM()
    27 {
    28     memset(linker,-1,sizeof(linker));
    29     memset(ly,0,sizeof(ly));
    30     for(int i=1;i<=nx;i++)
    31     {
    32         lx[i]=-INF;
    33         for(int j=1;j<=ny;j++)
    34         {
    35             if(G[i][j]>lx[i]) lx[i]=G[i][j];
    36         }
    37     }
    38     for(int x=1;x<=nx;x++)
    39     {
    40         for(int i=1;i<=ny;i++) slack[i]=INF;
    41         while(1)
    42         {
    43             memset(visx,0,sizeof(visx));
    44             memset(visy,0,sizeof(visy));
    45             if(dfs(x))    break;  //找到增广路,进入下一个点的增广 
    46         //如果失败,需要改变顶标使图中可行边数量增加
    47         //在所有的增广路的x顶标中减去常数d
    48         //在所有增广路的Y顶标中增加一个常数d
    49              int d=INF;
    50              for(int i=1;i<=ny;i++)
    51                  if(!visy[i]&&d>slack[i])
    52                      d=slack[i];
    53              for(int i=1;i<=nx;i++)
    54                  if(visx[i]) lx[i]-=d;
    55              for(int i=1;i<=ny;i++)
    56                  if(visy[i]) ly[i]+=d;
    57                  else slack[i]-=d;
    58         }
    59     }
    60     int res=0;
    61     for(int i=1;i<=ny;i++)
    62         if(linker[i]!=-1)
    63             res+=G[linker[i]][i];
    64     return res;
    65     
    66 }
    67 int main()
    68 {
    69     while(scanf("%d",&n)==1)
    70     {
    71         nx=ny=n;
    72         for(int i=1;i<=n;i++)
    73             for(int j=1;j<=n;j++)
    74                 scanf("%d",&G[i][j]);
    75         ans=KM();
    76         printf("%d
    ",ans);
    77     }
    78     return 0;
    79 }
  • 相关阅读:
    对象参数dojo异步编程之dojo/promise/all模块(dojo/DeferredList替代者)
    文件进程linux系统编程之文件与I/O(五):打开文件的内核结构file和重定向
    代码配置spring scala
    返回行javascript比较时间大小
    项目邮件[置顶] 失业的程序员(十二):潜意识的智商
    地址变形Uva 11401 Triangle Counting
    节点拓扑应用拓扑排序来解决DAG(directed acylic graph)的单源最短路径问题
    TortoiseGit使用入门
    RGMII
    ARM处理器系统初始化编程注意事项
  • 原文地址:https://www.cnblogs.com/aininot260/p/9439325.html
Copyright © 2011-2022 走看看