zoukankan      html  css  js  c++  java
  • 二分图、网络流模版总结

     

    这个星期重新回去理解二分图和网络流的算法,觉得当初真的是有点傻萌,都不太了解就直接套模版……

     

    关于二分图

    关于二分图的概念不想吐槽了,太多而且好多都好乱,看了很多个什么概念总结结果发现有的人把最大匹配叫最佳匹配。反正是看的我一脸蛋疼,决定还是抛开概念吧,看着像啥用啥得了。

    匈牙利算法:
    其实就是不断寻找增广路。那么什么是增广路呢?
    既然是二分图,我们先定义X集和Y集。然后我们对X集里面的点一个一个来,拿出一个没用过的X集的点(简记为i),找所有和它相连的Y集的点(简记为j),如果这个Y集的点没用过,那么这两个点在一起就行了,如果用过了,就dfs这个Y集中的点是被那个X集中的点用的(简记为i'),这时候还是一样,找所有和这个i'点相连的Y点……如果这样dfs很多次后终于找到一个Y集中的点是没有用到了,那么原来是j-i',j'-i''……现在就变成i(之前没用过)-j,i'-j',……,i''-j''(之前没用过),这样就多了一个匹配。

    procedure addedge(j,k:longint);
    begin
      inc(tot);
      edge[tot].toward:=k;
      edge[tot].next:=first[j];
      first[j]:=tot;
    end;
    
    function dfs(x:longint):boolean;
    var
      i,too:longint;
    begin
      i:=first[x];
      while i>0 do begin
        too:=edge[i].toward;
        if not chose[too] then begin
          chose[too]:=true;
          if (match[too]=0) or dfs(match[too]) then begin
            match[too]:=x;
            exit(true);
          end
        end;
        i:=edge[i].next;
      end;
      exit(false);
    end;
    
    procedure work;
    var
      ans,i:longint;
    begin
      ans:=0;
      for i:=1 to tot1 do begin
        fillchar(chose,sizeof(chose),false);
        if dfs(i) then inc(ans);
      end;
      writeln(ans);
    end;
    View Code

     

    KM算法
    很神奇。用来解决最大权匹配(雾之概念)。其实就是边加上了权,求某个匹配使权值最大。这类问题前提是必须存在一个方案使得可以让X每个点都在某个匹配里面。
    我们可以搞个标号lx(),ly(),如果有边i->j,那么保证lx(i)+ly(j)>cij(边的权值)。然后这样的标号方案是不唯一的,但是这样的标号一定满足大于等于所有匹配(所以就包括了那个最大的方案),那么我们可以找到一个最小的标号方案使它等于最大的方案。
    经过什么构造矩阵还是什么鬼的(搞了一个晚上搞懂了但是不想解释),我们可以得到一个流程。

    如果一个x点不能加进图里面,那么调整标号,取出那些所有不再dfs中走到的Y点,找到最小slack值(在dfs顺便处理出的,slack[j]=min{slack[j],lx[i]+ly[j]-cij},i为和j有边相连的点)。然后所有dfs中走到的X点的标号-最小slack值,所有走到的y点标号+最小slack值,同时slack数组也变了,也就是所有dfs中走到的Y点的slack值-最小slack值。再跑匈牙利,不行再改,直到可以。

    type
      arr=record
        toward,next,from,cost:longint;
      end;
    
    const
      maxm=1000000;
      maxn=50000;
    
    var
      edge:array[0..maxm]of arr;
      l,slack,first,match:array[0..maxn]of longint;//实际中我为了偷懒就直接开一个l,然后y集合的点编号统一加上n
      chose:array[0..maxn]of boolean;
      tot,n,m:longint;
    
    function min(x,y:longint):longint;
    begin
      if x<y then exit(x);
      exit(y);
    end;
    
    procedure addedge(j,k,l:longint);
    begin
      inc(tot);
      edge[tot].from:=j;
      edge[tot].toward:=k;
      edge[tot].cost:=l;
      edge[tot].next:=first[j];
      first[j]:=tot;
    end;
    
    function dfs(x:longint):boolean;
    var
      i,too,value:longint;
    begin
      chose[x]:=true;
      i:=first[x];
      while i>0 do begin
        too:=edge[i].toward;
        value:=edge[i].cost;
        if not chose[too] then
          if l[x]+l[too]=value then begin
            chose[too]:=true;
            if (match[too]=0) or dfs(edge[match[too]].from) then begin
              match[too]:=i;
              exit(true);
            end;
          end
          else
            slack[too]:=min(slack[too],l[x]+l[too]-value);
        i:=edge[i].next;
      end;
      exit(false);
    end;
    
    procedure relable;
    var
      i,delta:longint;
    begin
      delta:=maxlongint;
      for i:=n+1 to n+m do
        if not chose[i] then delta:=min(delta,slack[i]);
      for i:=1 to n do
        if chose[i] then dec(l[i],delta);
      for i:=n+1 to n+m do
        if chose[i] then inc(l[i],delta)
          else dec(slack[i],delta);
    end;
    
    function km:longint;
    var
      i,ans:longint;
    begin
      for i:=1 to n do begin
        fillchar(slack,sizeof(slack),$7f);
        while true do begin
          fillchar(chose,sizeof(chose),false);
          if dfs(i) then break;
          relable;
        end;
      end;
      ans:=0;
      for i:=n+1 to n+m do
        inc(ans,edge[match[i]].cost);
      exit(ans);
    end;
    
    procedure into;
    var
      i,j,k,h:longint;
    begin
      readln(n,m);
      for i:=1 to n do
        for j:=1 to m do begin
          read(k);
          addedge(i,j+n,k);
          if k>l[i] then l[i]:=k;
        end;
    end;
    
    begin
      into;
      writeln(km);
      readln;
      readln;
    end.
    View Code

     

    更多:
    二分图最大匹配的三个定理
       1:最大匹配数 + 最大独立集 = n + m
       2:二分图的最小覆盖数 = 最大匹配数
       3:最小路径覆盖 = 最大独立集
        最大独立集是指求一个二分图中最大的一个点集,该点集内的点互不相连。
        最小顶点覆盖是指 在二分图中,用最少的点,让所有的边至少和一个点有关联。
         最小路径覆盖是指一个不含圈的有向图G 中,G的一个路径覆盖是一个其结点不相交的路径集合P,图中的每一个结点仅包含于P 中的某一条路径。路径可以从任意结点开始和结束,且长度也为任意值,包括0.

    几种模型

       http://wenku.baidu.com/link?url=TiCZ5ILAyn7F5fHSKvwOxnr8J1TgCBOdHUtipi0E78fgVmXszj-8OMlgX8i4z-oPDFsPGbyM-3pvDYNweXwA5As7Lrn0CA7SVs71yZM6AAu

       文章后面总结的五个基本模型。

     

     

    网络流
    很多的问题都可以转为网络流求解,所以这是个万能的骗分算法(雾)。

    最大流
    学的是sap,觉得这个东西很好懂。现在再看的时候发现以前自己总结的那些资料非常好,一下子就回忆起来的。所以不想讲

    //蔡大神当初的模版
    const maxn=1000000;
    type edgetype=record
     toward,cap,next:longint;   //邻接表 toward是指向哪个点,cap是容量,next是下一条边。
    end;
    
    var
     cur,first,dist,num:array[0..maxn] of longint;  //cur为当前弧优化 first为该点指向的最后一条边(邻接表用) dist、num是用于层次数记录
     edge:array[0..maxn] of edgetype;               //邻接表
     n,m,s,t,tot,i,j,a,b,c:longint;             //tot是记边数
    
    function min(x,y:longint):longint;
    begin
     if x<y then exit(x) else exit(y);
    end;
    
    procedure add(i,j,k:longint);
    begin
     edge[tot].toward:=j;
     edge[tot].cap:=k;
     edge[tot].next:=first[i];
     first[i]:=tot;
     inc(tot);
    end;
    
    procedure addedge(i,j,k:longint);
    begin
     add(i,j,k);
     add(j,i,0);
    end;
    
    function sap(v,flow:longint):longint;
    var i,j,tmp,more,now:longint;
    begin
     if v=t then exit(flow);
     now:=0;
     i:=cur[v];                      //当前弧优化
     while i<>-1 do
      begin
       tmp:=edge[i].toward;
       if (edge[i].cap>0) and (dist[v]=dist[tmp]+1) then
        begin
         more:=sap(tmp,min(edge[i].cap,flow-now));
         dec(edge[i].cap,more);
         inc(edge[i xor 1].cap,more);
         inc(now,more);
         cur[v]:=i;
         if now=flow then exit(flow);
        end;
       i:=edge[i].next;
      end;
     dec(num[dist[v]]);
     if num[dist[v]]=0 then dist[s]:=t;   
    //这是个优化,成为gap优化。当该图的层次数中发现不存在某一层次,说明这个图已经断成两半了,根本无法连通,所以可以直接退出
     inc(dist[v]);
     inc(num[dist[v]]);
     cur[v]:=first[v];
     exit(now);
    end;
    
    function maxflow:longint;
    var i,flow:longint;
    begin
     fillchar(dist,sizeof(dist),0);
     fillchar(num,sizeof(num),0);
     flow:=0;
     num[0]:=t;
     for i:= 0 to n do cur[i]:=first[i];  //当前弧初始化
     while dist[s]<t do inc(flow,sap(s,maxlongint));
     exit(flow);
    end;
    
    procedure init;
    
    begin
     readln(n,m,s,t);    //n个点,m条边,s、t分别为起点终点
     for i:= 1 to n do first[i]:=-1;
     tot:=0;
     for i:=1 to m do
      begin
       readln(a,b,c);
       addedge(a,b,c);
      end;
    end;
    
    Begin
    init;
    writeln(maxflow);
    End.
    
    附测试用数据:
    Data:
    6 10 1 6
    1 3 3
    1 4 2
    1 2 3
    4 2 1
    3 4 1
    2 5 4
    5 4 1
    3 6 2
    4 6 4
    5 6 1
    
    Ans:7
    View Code

     

    最小费用最大流
    spfa大法和zkw大法当时都学了。
    而且当时蔡大神总结的也非常好(点赞)。所以也不想讲了。
    zkw大法的话其实还是用标号思想啦,不行就调整在找,找到不行有调整再搞……然后关于跑有负边的zkw费用流,当时并不是很理解。现在再看重新想了下终于懂了……其实还是一样,同样是满足一个标号,Di≥Dj+cij(i->j),一般情况是当边是正的时D是从s到t递减,不断增大Di以求更多的边。现在边有负,所以是从s到t递增,解决方案很简单,先算出t到s每个点D是正数最小是多少,初始化一下标号,还是可以套用以前的式子。

    扔个有负边的

    //vijos1525 生命之泉
    type
      arr=record
        toward,cap,cost,next:longint;
      end;
    
    const
      maxn=50000;
    
    var
      edge:array[0..10000]of arr;
      first,dist,slack:array[0..5000]of longint;
      q:array[0..maxn]of longint;
      chose:array[0..5000]of boolean;
      i,j,k,l,s,t,n,m,tot,maxcost,maxflow,kk:longint;
    
    procedure add(i,j,k,l:longint);
    begin
      edge[tot].toward:=j;
      edge[tot].cap:=k;
      edge[tot].cost:=l;
      edge[tot].next:=first[i];
      first[i]:=tot;
      inc(tot);
    end;
    
    procedure addedge(i,j,k,l:longint);
    begin
      add(i,j,k,l);
      add(j,i,0,-l);
    end;
    
    function min(x,y:longint):longint;
    begin
      if x>y then exit(y);
      exit(x);
    end;
    
    procedure spfa;
    var
      head,tail,i,too,value:longint;
    begin
      fillchar(chose,sizeof(chose),false);
      for i:=0 to t do dist[i]:=maxlongint;
      head:=1;
      tail:=1;
      q[1]:=s;
      chose[s]:=true;
      dist[s]:=0;
      while head<=tail do begin
        j:=q[head];
        i:=first[j];
        while i<>-1 do begin
          too:=edge[i].toward;
          value:=edge[i].cost;
          if (edge[i].cap>0) and (dist[too]>dist[j]+value) then begin
            dist[too]:=dist[j]+value;
            if not chose[too] then begin
              inc(tail);
              if tail=maxn then tail:=1;
              q[tail]:=too;
              chose[too]:=true;
            end;
          end;
          i:=edge[i].next;
        end;
        inc(head);
        if head=maxn then head:=1;
        chose[j]:=false;
      end;
    end;
    
    function aug(v,flow:longint):longint;
    var
      rec,ret,too,value,i:longint;
    begin
      if v=t then begin
        inc(maxcost,(dist[t]-dist[s])*flow);
        inc(maxflow,flow);
        exit(flow);
      end;
      i:=first[v];
      chose[v]:=true;
      rec:=0;
      while i<>-1 do begin
        too:=edge[i].toward;
        value:=edge[i].cost;
        if (edge[i].cap>0) and (not chose[too]) then
          if dist[v]=dist[too]+value then begin
            ret:=aug(too,min(flow-rec,edge[i].cap));
            dec(edge[i].cap,ret);
            inc(edge[i xor 1].cap,ret);
            inc(rec,ret);
            if rec=flow then exit(flow);
          end else
            slack[too]:=min(slack[too],dist[too]+value-dist[v]);
        i:=edge[i].next;
      end;
      exit(rec);
    end;
    
    function rel:boolean;
    var
      spent,i:longint;
    begin
      spent:=maxlongint;
      for i:=0 to t do
        if not chose[i] then spent:=min(spent,slack[i]);
      if spent=maxlongint then exit(false);
      for i:=0 to t do
        if chose[i] then inc(dist[i],spent);
      exit(true);
    end;
    
    procedure costflow;
    begin
      spfa;
      for i:=0 to t do
        dist[i]:=-dist[i];
      repeat
        for i:=0 to t do slack[i]:=maxlongint;
        repeat
          fillchar(chose,sizeof(chose),false);
        until aug(s,maxlongint)<=0;
      until not rel;
    end;
    
    procedure into;
    begin
      readln(n,m,kk);
      s:=0;
      t:=n+2;
      tot:=0;
      for i:=0 to t do first[i]:=-1;
      addedge(1,t,kk,0);
      addedge(s,n+1,kk,0);
      for i:=1 to m do begin
        readln(j,k,l);
        addedge(k+1,j,1,-l);
      end;
      for i:=1 to n do
        addedge(i+1,i,maxlongint,0);
    end;
    
    Begin
      into;
      costflow;
      writeln(maxcost);
      readln;
      readln;
    End.
    View Code

     

    关于更多

    有上下界的网络流,费用流:这个看书上说的balabala说是要见好多个附加网络神马的,然后看hzw大神的代码发现根本不需要那么麻烦,似乎只有多加个超汇点和超源点判断是否可行,建边要拆开,上界-下界的结果直接建,然后下界起点连超汇点,超源点连边终点,然后再用原源点和原汇点跑一边,两次答案相加就是最大流答案了?

    以前做了好多还是用几天时候回顾下看看当时是不是比较傻就直接过了?

  • 相关阅读:
    自解压命令学习
    浅谈JAVA中的final修饰符
    clearfix清除浮动进化史
    HTML5本地存储Localstorage
    Log4j详细使用教程
    AutoCompleteTextView与MultiAutoCompleteTextView应用
    冒泡排序
    方法的递归
    枚举类型
    ThinkJs笔记琐碎
  • 原文地址:https://www.cnblogs.com/Macaulish/p/4202356.html
Copyright © 2011-2022 走看看