【费用流】hdu1533 poj2516 bzoj1070 bzoj1061
2016-01-03 12:33
330 查看
费用流是在网络流的基础上求流最大的前提下使得费用最小(或者最大)。
算法一:SPFA寻找增广路
在isap算法中,是当dis[v]+1==dis[u]时才访问v。即边(u,v)的边权为1。在这里令边权为流过该边的费用即可。
算法二:zkw费用流
当寻找到增广路使得源点与汇点分开时(即不存在dis[v]+w(u,v)==dis[u]),在以源点为集合中找到一个最小差值。将源点集合中的节点的dis全部加上该差值。
hdu1533
题目大意:在n∗m的方格上有若干个房子和人,每个房子只能住一个人。人只能向上下左右移动。求使得所有人都有房子住的最小距离和。
这是一个很明显的二分图,可以用KM算法完成最有匹配。
当然可以用费用流。由于是一个二分图,增广路不会很长,用zkw费用流更快。
poj2516
题目大意:有n个店铺要进k种货,有m个厂库。已知每个店铺对每种货的需求量,每个厂库每种货的库存以及每个厂库到每个店铺的单位费用。求最小费用。
如果只有一种货物,就是一个二分图的最优匹配。
所有对这k种货物分别处理,将费用加起来即可。
bzoj1070[scoi2007]
省选难度的建模果真不一样…
想到贪心的请注意一下数据范围,这么小贪心的可能性不太大…
不知道非要来除以一个总数有何意义(可能是为了迷惑那些想用分数规划的人吧)
言归正传,如何建模是这道题的难点。
考虑自己对别人的影响。有一点点逆向思维的感觉。
若第i个车被第j个技术人员倒数第k个修,那么对答案的贡献就是k∗cost[i][j]。它会使后面k−1个车多等cost[i][j],同时它自己耗时cost[i][j]。
所以说就以这种状态建立节点。
蒟蒻一开始就想到了费用流,感觉应该要拆点,又不知道怎么拆。之后想到了将每个技术人员拆成m(m为顾客数)个点,然后就不知道怎样定义边权……
特别注意区分n,m的意义,要不然调半天都不知道哪里错了…
bzoj1061[noi2008]
建模神题…看到题根本就木有思路啊( ⊙ o ⊙ )
推荐byvoid大神的题解 题解链接
有大牛说这是一道单纯形法的裸题 233
说说我的理解吧
利用网络流的流量平衡建立等式,正的变量是流入该点的流量,负的变量是流出该点的流量。
算法一: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;
}
相关文章推荐
- [网络流24题 #16]数字梯形问题
- [网络流24题 #18]分配问题
- [网络流24题 #18]分配问题
- 收益最大
- poj 3422 费用流
- UVA 10806 Dijkstra, Dijkstra.(最小费用最大流)
- 【BeiJing wc2012】【BZOJ2661】连连看
- HDU 4807 Lunch Time
- NOI 2008 志愿者招募
- HDU 4406 GPA 最大费用流
- HDU 4411 Arrest 最小费用流
- HDU 1533 Going Home 最小费用流
- HDU 3488 Tour 费用流
- HDU 3435 A new Graph Game 费用流
- HDU 1853 Cyclic Tour 费用流
- HDU 2686 Matrix 费用流
- HDU 3376 Matrix Again 费用流
- HDU 3667 Transportation 费用流
- HDU 3315 My Brute 费用流
- HDU 3395 Special Fish 费用流