费用流是在网络流的基础上求流最大的前提下使得费用最小(或者最大)。
算法一:SPFA寻找增广路
在isap算法中,是当 dis[v]+1==dis[u] 时才访问 v 。即边 (u,v) 的边权为 1 。在这里令边权为流过该边的费用即可。
bool spfa()
{
queue<int>q;
for(int i=b;i<=e;++i)dis[i]=inf, pre[i]=i;
q.push(b), dis[b]=0;
int u, v;
while(!q.empty())
{
u=q.front();
q.pop();
in[u]=0;
for(node *p=adj[u];p!=NULL;p=p->next)
if(p->cap>0&&dis[(v=p->v)]>dis[u]+p->w)
{
dis[v]=dis[u]+p->w;
pre[v]=u, temp[v]=p;
if(!in[v])q.push(v), in[v]=1;
}
}
return dis[e]!=inf;
}
int solve()
{
int ans=0, delta;
while(spfa())
{
delta=inf;
for(int i=e;i!=b;i=pre[i])
delta=min(delta,temp[i]->cap);
for(int i=e;i!=b;i=pre[i])
{
temp[i]->cap-=delta, temp[i]->back->cap+=delta;
temp[i]->back->w=-temp[i]->w;
ans+=delta*temp[i]->w;
}
}
return ans;
}
算法二:zkw费用流
当寻找到增广路使得源点与汇点分开时(即不存在 dis[v]+w(u,v)==dis[u] ),在以源点为集合中找到一个最小差值。将源点集合中的节点的 dis 全部加上该差值。
bool label()
{
int mv=inf;
for(int i=b;i<=e;++i)
if(vis[i])
for(node *p=adj[i];p!=NULL;p=p->next)
if(!vis[p->v]&&p->cap>0)
mv=min(mv,p->w+vis[p->v]-vis[i]);
if(mv==inf)return 0;
for(int i=b;i<=e;++i)
if(vis[i])dis[i]+=mv;
return 1;
}
int aug(int u,int augco)
{
if(u==e)
{
ans+=dis[b]*augco;
return augco;
}
vis[u]=1;
int augc=augco, delta, v;
for(node *p=adj[u];p!=NULL;p=p->next)
{
v=p->v;
if(!vis[v]&&p->cap>0&&dis[v]+p->w==dis[u])
{
delta=aug(v,min(augc,p->cap));
p->cap-=delta, p->back->cap+=delta, augc-=delta;
}
}
return augco-augc;
}
hdu1533
题目大意:在 n∗m 的方格上有若干个房子和人,每个房子只能住一个人。人只能向上下左右移动。求使得所有人都有房子住的最小距离和。
这是一个很明显的二分图,可以用KM算法完成最有匹配。
当然可以用费用流。由于是一个二分图,增广路不会很长,用zkw费用流更快。
#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 105
#define MAXM 100005
#define abs(a) ((a)>0?(a):(-(a)))
#define min(a,b) ((a)<(b)?(a):(b))
#define inf 0x3f3f3f3f
using namespace std;
char Map[MAXN][MAXN];
int n, m, ans, dis[MAXN<<1], b, e;
int human[MAXN][2], house[MAXN][2], cnt, cnth;
bool vis[MAXN<<1];
struct node
{
int v, w, cap;
node *front, *next;
inline void init(){v=w=cap=0, front=next=0;}
}edge[MAXM<<1], *adj[MAXN<<1], *pos=edge, *temp[MAXN<<1];
inline void add(int a,int b,int c,int d)
{
pos->v=b, pos->w=c, pos->cap=d, pos->next=adj[a], pos->front=pos+1;
adj[a]=pos;
++pos;
pos->v=a, pos->w=-c, pos->cap=0, pos->next=adj[b], pos->front=pos-1;
adj[b]=pos;
++pos;
}
int cal(int a1,int b1,int a2,int b2){return abs(a1-a2)+abs(b1-b2);}
void build()
{
memset(adj,0,sizeof adj);
memset(dis,0,sizeof dis);
for(node *p=edge;p!=pos;++p)p->init();
pos=edge;
for(int i=1;i<=cnt;++i)
for(int j=1;j<=cnth;++j)
add(i,j+cnt,cal(human[i][0],human[i][1],house[j][0],house[j][1]),1);
for(int i=1;i<=cnt;++i)add(0,i,0,1);
n=cnt+cnth+1;
for(int i=cnt+1;i<=cnth+cnt;++i)add(i,n,0,1);
b=0, e=n;
}
bool lable()
{
int mv=inf;
for(int i=b;i<=e;++i)
if(vis[i])
for(node *p=adj[i];p!=NULL;p=p->next)
if(!vis[p->v]&&p->cap>0)
mv=min(mv,p->w+dis[p->v]-dis[i]);
if(mv==inf)return 0;
for(int i=b;i<=e;++i)
if(vis[i])dis[i]+=mv;
return 1;
}
int aug(int u,int augco)
{
if(u==e)
{
ans+=dis[b]*augco;
return augco;
}
int augc=augco, delta, v;
vis[u]=1;
for(node *p=adj[u];p!=NULL&&augc;p=p->next)
if(!vis[(v=p->v)]&&p->cap>0&&dis[u]==dis[v]+p->w)
{
delta=aug(v,min(p->cap,augc));
p->cap-=delta, p->front->cap+=delta;
augc-=delta;
}
return augco-augc;
}
int main()
{
while(~scanf("%d%d",&n,&m)&&n+m)
{
ans=cnt=cnth=0;
for(int i=1;i<=n;++i)
scanf("%s",Map[i]+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(Map[i][j]=='m')
human[++cnt][0]=i, human[cnt][1]=j;
else if(Map[i][j]=='H')
house[++cnth][0]=i, house[cnth][1]=j;
build();
do
{
do{memset(vis,0,sizeof vis);}
while(aug(b,inf));
}while(lable());
printf("%d\n",ans);
}
return 0;
}
poj2516
题目大意:有 n 个店铺要进 k 种货,有 m 个厂库。已知每个店铺对每种货的需求量,每个厂库每种货的库存以及每个厂库到每个店铺的单位费用。求最小费用。
如果只有一种货物,就是一个二分图的最优匹配。
所有对这 k 种货物分别处理,将费用加起来即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#define inf 2147483647
#define MAXN 55
#define MAXM 5005
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n, m, k, b, e, cnt;
int d[MAXN][MAXN], need[MAXN][MAXN], have[MAXN][MAXN];
int dis[MAXN<<1], ans;
struct node
{
int v, w, cap;
node *front, *next;
inline void init()
{
v=w=cap;
front=next=0;
}
}edge[MAXM<<1], *adj[MAXN<<1], *pos=edge;
inline void add(int a,int b,int c,int d)
{
pos->v=b, pos->w=c, pos->cap=d, pos->front=pos+1, pos->next=adj[a];
adj[a]=pos;
++pos;
pos->v=a, pos->w=-c, pos->cap=0, pos->front=pos-1, pos->next=adj[b];
adj[b]=pos;
++pos;
}
bool vis[MAXN<<1];
void build()
{
memset(dis,0,sizeof dis);
b=0, e=n+m+1;
for(node *p=edge;p!=pos;++p)p->init();
pos=edge;
memset(adj,0,sizeof adj);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
add(j,i+m,d[i][j],inf);
for(int i=1;i<=m;++i)add(0,i,0,have[i][cnt]);
for(int i=1;i<=n;++i)add(i+m,e,0,need[i][cnt]);
}
bool label()
{
int mv=inf;
for(int i=b;i<=e;++i)
if(vis[i])
for(node *p=adj[i];p!=NULL;p=p->next)
if(!vis[p->v]&&p->cap>0)
mv=min(mv,p->w+dis[p->v]-dis[i]);
if(mv==inf)return 0;
for(int i=b;i<=e;++i)
if(vis[i])dis[i]+=mv;
return 1;
}
int aug(int u,int augco)
{
if(u==e)
{
ans+=dis[b]*augco;
return augco;
}
vis[u]=1;
int augc=augco, delta, v;
for(node *p=adj[u];p!=NULL;p=p->next)
{
v=p->v;
if(!vis[v]&&p->cap>0&&dis[v]+p->w==dis[u])
{
delta=aug(v,min(p->cap,augc));
p->cap-=delta, augc-=delta;
p->front->cap+=delta;
}
}
return augco-augc;
}
bool check()
{
int sum;
for(int i=1;i<=k;++i)
{
sum=0;
for(int j=1;j<=n;++j)
sum-=need[j][i];
for(int j=1;j<=m&&sum<0;++j)
sum+=have[j][i];
if(sum<0)return 1;
}
return 0;
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&k)&&n+m+k)
{
ans=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=k;++j)
scanf("%d",&need[i][j]);
for(int i=1;i<=m;++i)
for(int j=1;j<=k;++j)
scanf("%d",&have[i][j]);
if(check())
{
for(cnt=1;cnt<=k;++cnt)
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&d[i][j]);
puts("-1");
continue;
}
for(cnt=1;cnt<=k;++cnt)
{
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
scanf("%d",&d[i][j]);
build();
do
{
do{memset(vis,0,sizeof vis);}while(aug(b,inf));
}while(label());
}
printf("%d\n",ans);
}
return 0;
}
bzoj1070[scoi2007]
省选难度的建模果真不一样…
想到贪心的请注意一下数据范围,这么小贪心的可能性不太大…
不知道非要来除以一个总数有何意义(可能是为了迷惑那些想用分数规划的人吧)
言归正传,如何建模是这道题的难点。
考虑自己对别人的影响。有一点点逆向思维的感觉。
若第 i 个车被第 j 个技术人员倒数第 k 个修,那么对答案的贡献就是 k∗cost[i][j] 。它会使后面 k−1 个车多等 cost[i][j] ,同时它自己耗时 cost[i][j] 。
所以说就以这种状态建立节点。
蒟蒻一开始就想到了费用流,感觉应该要拆点,又不知道怎么拆。之后想到了将每个技术人员拆成 m ( m 为顾客数)个点,然后就不知道怎样定义边权……
特别注意区分 n,m 的意义,要不然调半天都不知道哪里错了…
#include <iostream>
#include <cstdio>
#include <cstring>
#define inf 2147483647
#define MAXN 605
#define MAXM 100005
using namespace std;
int n, m, b, e, ans;
int dis[MAXN], cost[61][10];
bool vis[MAXN];
struct node
{
int v, w, cap;
node *front, *next;
inline void init()
{v=w=cap=0, front=next=0;}
}edge[MAXM<<1], *adj[MAXN], *pos=edge;
inline void add(int a,int b,int c,int d)
{
pos->v=b, pos->w=c, pos->cap=d, pos->front=pos+1, pos->next=adj[a];
adj[a]=pos;
++pos;
pos->v=a, pos->w=-c, pos->cap=0, pos->front=pos-1, pos->next=adj[b];
adj[b]=pos;
++pos;
}
bool label()
{
int mv=inf;
for(int i=b;i<=e;++i)
if(vis[i])
for(node *p=adj[i];p!=NULL;p=p->next)
if(!vis[p->v]&&p->cap>0)
mv=min(mv,dis[p->v]+p->w-dis[i]);
if(mv==inf)return 0;
for(int i=b;i<=e;++i)
if(vis[i])
dis[i]+=mv;
return 1;
}
int aug(int u,int augco)
{
if(u==e)
{
ans+=dis[b]*augco;
return augco;
}
vis[u]=1;
int augc=augco, delta, v;
for(node *p=adj[u];p!=NULL;p=p->next)
{
v=p->v;
if(!vis[v]&&p->cap>0&&dis[v]+p->w==dis[u])
{
delta=aug(v,min(p->cap,augc));
p->cap-=delta, p->front->cap+=delta;
augc-=delta;
}
}
return augco-augc;
}
void build()
{
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
for(int k=1;k<=m;++k)
add(k,m*i+j,cost[k][i]*j,1);
b=0, e=n*m+m+1;
for(int i=m+1;i<e;++i)add(i,e,0,1);
for(int i=1;i<=m;++i)add(0,i,0,1);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j)
scanf("%d",&cost[i][j]);
build();
do
{
do{memset(vis,0,sizeof vis);}while(aug(b,inf));
}while(label());
printf("%.2lf\n",ans/double(m));
return 0;
}
bzoj1061[noi2008]
建模神题…看到题根本就木有思路啊( ⊙ o ⊙ )
推荐byvoid大神的题解 题解链接
有大牛说这是一道单纯形法的裸题 233
说说我的理解吧
利用网络流的流量平衡建立等式,正的变量是流入该点的流量,负的变量是流出该点的流量。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define inf 2147483647
#define MAXN 1005
#define MAXM 10005
using namespace std;
int n, m, b, e, ans;
int dis[MAXN], need[MAXN], vol[MAXM][3], pre[MAXN];
bool in[MAXN];
struct node
{
int v, w, cap;
node *back, *next;
}edge[MAXM<<2], *adj[MAXN], *pos=edge, *temp[MAXN];
inline void add(int a,int b,int c,int d)
{
pos->v=b, pos->w=c, pos->cap=d, pos->back=pos+1, pos->next=adj[a];
adj[a]=pos;
++pos;
pos->v=a, pos->w=-c, pos->cap=0, pos->back=pos-1, pos->next=adj[b];
adj[b]=pos;
++pos;
}
bool spfa()
{
queue<int>q;
for(int i=b;i<=e;++i)dis[i]=inf, pre[i]=i;
q.push(b), dis[b]=0;
int u, v;
while(!q.empty())
{
u=q.front();
q.pop();
in[u]=0;
for(node *p=adj[u];p!=NULL;p=p->next)
if(p->cap>0&&dis[(v=p->v)]>dis[u]+p->w)
{
dis[v]=dis[u]+p->w;
pre[v]=u, temp[v]=p;
if(!in[v])q.push(v), in[v]=1;
}
}
return dis[e]!=inf;
}
int solve()
{
int ans=0, delta;
while(spfa())
{
delta=inf;
for(int i=e;i!=b;i=pre[i])
delta=min(delta,temp[i]->cap);
for(int i=e;i!=b;i=pre[i])
{
temp[i]->cap-=delta, temp[i]->back->cap+=delta;
temp[i]->back->w=-temp[i]->w;
ans+=delta*temp[i]->w;
}
}
return ans;
}
void build()
{
b=0, e=n+2;
for(int i=1;i<=n+1;++i)
if(need[i]-need[i-1]>=0)add(b,i,0,need[i]-need[i-1]);
else add(i,e,0,need[i-1]-need[i]);
for(int i=1;i<=m;++i)
add(vol[i][0],vol[i][1]+1,vol[i][2],inf);
for(int i=1;i<=n;++i)
add(i+1,i,0,inf);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)scanf("%d",&need[i]);
for(int i=1;i<=m;++i)scanf("%d%d%d",&vol[i][0],&vol[i][1],&vol[i][2]);
build();
printf("%d\n",solve());
return 0;
}