培训补坑(day4:网络流建模与二分图匹配)

补坑时间到QAQ

好吧今天讲的是网络流建模与二分图匹配。。。

day3的网络流建模好像说的差不多了、(囧)

那就接着补点吧。。

既然昨天讲了建图思想,那今天就讲讲网络流最重要的技巧:拆点。

拆点,顾名思义,就是把一个状态拆成数个点以满足题目要求。

今天主要围绕一个例题来讲:修车。(虽然是丧题,但是却是网络流算法思想实现的典例)

——————————————————我是分割线——————————————————

题目:

同一时刻有位车主带着他们的爱车来到了汽车维修中心。维修中心共有M位技术人员,不同的技术人员对不同的车进行维修所用的时间是不同的。现在需要安排这M位技术人员所维修的车及顺序,使得顾客平均等待的时间最小。 说明:顾客的等待时间是指从他把车送至维修中心到维修完毕所用的时间。注:(2<=m<=9,1<=n<=60)

——————————————————我是分割线——————————————————

其实我一开始看到这题,我都没看出来这是网络流(真是太弱了)

然后我去网络上看了题解。

首先我们知道一个工人修一辆车就会让其余没有修过车的time增加,所以我们发现如果考虑第i个工人修第j辆车的话,会发现时间是不确定的。

但是如果我们倒着过来计算的话,那么我们就能够计算时间了。

所以我们将n个工人每一个分为m个点,表示第i个工人修倒数第j辆车,然后每一条边的费用就是time*j,表示等待的时间,从S到每一个点连边,从每一个点到T连边,然后我们跑最小费用最大流就好了。

对于费用流不懂的同学们可以去我的博客上看看。(暂时没写,以后补坑)

下面附上例题代码

#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
int ans;
int go[200005],tot,S,T,first[200005],next[200005],flow[200005];
int cost[200005],c[200005],edge[200005],from[200005],a[500][500];
int nodes,b[500][500],n,m,op[200005],dis[200005],vis[200005];
int read(){
    int t=0,f=1;char ch=getchar();
    while (ch<‘0‘||ch>‘9‘){if (ch==‘-‘) f=-1;ch=getchar();}
    while (‘0‘<=ch&&ch<=‘9‘){t=t*10+ch-‘0‘;ch=getchar();}
    return t*f;
}
void insert(int x,int y,int z,int l){
    tot++;
    go[tot]=y;
    next[tot]=first[x];
    first[x]=tot;
    flow[tot]=z;
    cost[tot]=l;
}
void add(int x,int y,int z,int l){
    insert(x,y,z,l);op[tot]=tot+1;
    insert(y,x,0,-l);op[tot]=tot-1;
}
bool spfa(){
    for (int i=0;i<=T;i++) dis[i]=0x3f3f3f3f,vis[i]=0;
    dis[S]=0;
    int h=1,t=1;
    c[1]=S;
    while (h<=t){
        int now=c[h++];
        for (int i=first[now];i;i=next[i]){
            int pur=go[i];
            if (flow[i]&&dis[pur]>dis[now]+cost[i]){
                dis[pur]=dis[now]+cost[i];
                from[pur]=now;
                edge[pur]=i;
                if (vis[pur]) continue;
                vis[pur]=1;
                c[++t]=pur;
            }
        }
        vis[now]=0;
    }
    return dis[T]!=0x3f3f3f3f;
}
void updata(){
    int mn=0x7fffffff;
    for (int i=T;i!=S;i=from[i]){
        mn=std::min(mn,flow[edge[i]]);
    }
    for (int i=T;i!=S;i=from[i]){
        flow[edge[i]]-=mn;
        flow[op[edge[i]]]+=mn;
        ans+=mn*cost[edge[i]];
    }
}
int main(){
    m=read();n=read();
    for (int i=1;i<=n;i++){
        for (int j=1;j<=m;j++){
            a[i][j]=read();
        }
    }
    S=0,T=n*m+n+1;
    for (int i=1;i<=n;i++)
     add(S,i,1,0);
    nodes=n;
    for (int i=1;i<=m;i++)
      for (int k=1;k<=n;k++)
       b[i][k]=++nodes,add(nodes,T,1,0);
    for (int i=1;i<=n;i++)
     for (int j=1;j<=m;j++)
      for (int k=1;k<=n;k++)
       add(i,b[j][k],1,k*a[i][j]);
    while (spfa()) updata();
    double Ans=(double)ans/n;
    printf("%.2f\n",Ans);
}

接下来我们讲讲二分图匹配。

二分图匹配就是说有一个二层图,层数不同的两个点之间有一些边,问如何选取一些边,使得一个点只被一条边选中,而且边的个数最多(求最大匹配)

其中最主要的算法就是匈牙利算法,朴素而迅速。

我们上图。

对于这个图我们先选到这一条边

然后我们找到下一个点,发现它连接的一条边的终点已经被选中了,所以我们试着让第一个点重新匹配一个没有被匹配过的节点

然后我们找到第三个节点,发现可以匹配。

当我们找到第四个节点时,发现它所对应的节点没有办法修改匹配,至此匈牙利算法结束,最大匹配为3

而在我们进行匈牙利算法的时候有一些优化可以实现,比如如果一个点已经尝试匹配过,而且失败了,那么我们就不要尝试匹配了。(类似dfs减枝)

下面贴上算法

bool match(int u){
    S[u]=1;
    for(int i=head[u];i;i=g[i].next)
        if(!T[g[i].to]){
            T[g[i].to]=1;
            if(!lky[g[i].to]||match(lky[g[i].to])){
                lky[g[i].to]=u;lkx[u]=g[i].to;return true;
            }
        }
    return false;
}

注:现在起所有的算法如果我没有写例题我都只会列出核心代码,剩下的东西请视情况补充。QAQ

时间: 2024-08-28 23:15:41

培训补坑(day4:网络流建模与二分图匹配)的相关文章

培训补坑(day3:网络流&amp;最小割)

继续补坑.. 第三天主要是网络流 首先我们先了解一下网络流的最基本的算法:dinic 这个算法的主要做法就是这样的: 在建好的网络流的图上从源点开始向汇点跑一遍BFS,然后如果一条边的流量不为0,那么就往下标号, 每一个点的level都是上一个点的level+1 然后在跑一遍DFS,如果发现边的两个点的level差值为1(终点比起点的level大),那么就走这条边. 那么我们首先要了解一下如何建边 网络流的最基本概念就是:可以反悔 就是说假如说我们有更好的方案,那么我们可以把原来流掉的流量再流回

培训补坑(day7:线段树的区间修改与运用)(day6是测试,测试题解以后补坑QAQ)

补坑咯~ 今天围绕的是一个神奇的数据结构:线段树.(感觉叫做区间树也挺科学的.) 线段树,顾名思义就是用来查找一段区间内的最大值,最小值,区间和等等元素. 那么这个线段树有什么优势呢? 比如我们要多次查询1-n中的最大值,那么我们如果使用暴力来查找,那么我们每次查找的复杂度就是O(n) 但是如果我们把一个个区间变成树上的一个个点,并且我们严格保证树的深度,那么我们每次查找的复杂度就是O(logn) 这样就能让查询变得更快. 我们先简单讲一下线段树的存储(图中的标号就是线段树数组标号) 这就是线段

培训补坑(day5:最小生成树+负环判断+差分约束)

补坑补坑((╯‵□′)╯︵┻━┻) 内容真的多... 一个一个来吧. 首先是最小生成树. 先讲一下生成树的定义 生成树就是在一张图上选取一些边,使得整个图上所有的点都连通. 那么我们要求的最小生成树有两种算法可以求:1.prim算法,2.kruskal算法 我们先讲讲prim算法 prim算法有点像最短路中的dijstra,操作都几乎一样,原理就是从所有在队列中的点出发,找到最小的一条边,并把它连起来,这样子能够保证局部最优性. 在此我不讲这种算法,不过有兴趣的人可以去学一学. 我重点推出的是k

11082 - Matrix Decompressing (网络流建模|二分图匹配)

该题是一道经典的二分图匹配的题目 .现在终于有点明白什么是二分图匹配了,其实说白了就是依赖于最大流算法之上的一种解决特定问题的算法 . 所谓二分图,就是我们假定有两个集合A和B,每个集合中有若干元素(点),其中源点与A相连,汇点与B相连,并且他们的总容量决定了最终答案的上限,所以一定要维护好 . 然后由A中的点向B中的点连线,他们之间也有一定的容量制约关系(具体看题目中的边权值限制).这样就可以求出最大流量匹配了. 有时我们要求完美匹配,即所有流入的量等于流出的量  . 该题构思极其巧妙,因为我

POJ2584 T-Shirt Gumbo 二分图匹配(网络流)

1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 const int inf=0x3f3f3f3f; 6 const int sink=30; 7 8 struct Edge 9 { 10 int to; 11 int next; 12 int capacity; 13 14 void assign(int t,int n,int c) 15 { 16 to=t; next=n; ca

[NetworkFlow]网络流建模相关

流 网络流问题本质上是线性规划问题的应用之一,线性规划问题的标准形式是给出一组等式约束和不等式约束,要求最优化一个线性函数. 在流问题中,变量以流量的形式出现在问题中,我们给出一个流网络(以有向图的形式)来解决有关流的问题. 流是整个网络流问题的核心所在,它实际上是定义在流网络上的一个线性函数,在流网络中,每条边都有一个流量f(u,v),流f=∑v∈Vf(S,v) 流量f(u,v)是流问题中的变量,它有两个约束,一个是不等式,一个是等式 (1)容量限制:f(u,v)≤c(u,v) (2)流量平衡

UVALive-3268 Jamie&#39;s Contact Groups (最大流,网络流建模)

题目大意:你的手机通讯录里有n个联系人,m个分组,其中,有的联系人在多个分组里.你的任务是在一些分组里删除一些联系人,使得每个联系人只在一个分组里并且使人数最多的那个分组人数最少.找出人数最多的那个分组中的人数. 题目分析:要求的是最小的最大值,二分枚举这个最小的最大人数x.增加源点s和汇点t,从s向每一个联系人连一条弧,容量为1,表示一个联系人只能在一个分组中:然后对于每个联系人向他所在的分组连一条弧,容量为1,表示在这个分组里最多保存一次该联系人:然后从每个分组向汇点连一条弧,容量为x,表示

二分图与网络流 带权二分图的最大匹配

二分图与网络流  带权二分图的最大匹配 在某书上偶然发现,二分图和网络流是有联系的,在子图u中建立超级源点,在子图v中建立超级汇点,源点到u和汇点到v的每条边容量设为1,u和v中的边的容量也设为1,求出最大流也就是原二分图的最大匹配了. 而求带权二分图的最大匹配也就很容易了,将u和v的权值设为容量,仍然建立超级源点和超级汇点转为网络流解决即可. 真是一切皆可网络流啊...

cogs_14_搭配飞行员_(二分图匹配+最大流,网络流24题#01)

描述 http://cojs.tk/cogs/problem/problem.php?pid=14 有一些正飞行员和副飞行员,给出每个正飞行员可以和哪些副飞行员一起飞.一架飞机上必须一正一副,求最多多少飞机可以飞. 分析 裸的二分图匹配... 请叫我水题小王子... 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int maxn=100+5,INF=0x7fffffff; 5 int n,m,cnt=1; 6 int l