费用流:餐巾计划
很少会在这里会专门为了一道题而写点东西。毕竟只想把这里当作个人关于算法与数据结构/算法竞赛相关的知识整理的地方,不想弄得太杂、太乱。但是仰慕这道题很久了,而且为了这道题思路确实很巧妙。更重要的是,虽然此题除了10行左右的建模就是一个裸的最小费用最大流,但是我做这题前后花了不下6小时,费用流敲了3遍,每个程序都一个一个字母看过去,结果最后发现都犯了最傻的错误,有的少了一个+ 1,有的只是scanf里少了一个%d……
做这题时,发现自己之前还没有写过C语言的AC的费用流版本,这题就当作模板吧。
关于此题的解法,懒的写了,但为了以后查询方便还是复制一下别人的,解法除了源代码外基本都是来自http://www.byvoid.com/blog/lpf24-solution/
此题有贪心解,具体看中学高级本3.7(你可以搜索“中学高级本”找到这份doc文档,不过内容确实不怎么样,而且错误不少)。看看源程序都上百行,而且讲解得不太好,我就没看。
=================以下内容来自中学高级本===================
【问题描述】
一个餐厅在相继的N天里,第i天需要Ri块餐巾(i=l,2,…,N)。餐厅可以从三种途径获得餐巾。
(1)购买新的餐巾,每块需p分;
(2)把用过的餐巾送到快洗部,洗一块需m天,费用需f分(f<p)。如m=l时,第一天送到快洗部的餐巾第二天就可以使用了,送慢洗的情况也如此。
(3)把餐巾送到慢洗部,洗一块需n天(n>m),费用需s分(s<f)。
在每天结束时,餐厅必须决定多少块用过的餐巾送到快洗部,多少块送慢洗部。在每天开始时,餐厅必须决定是否购买新餐巾及多少,使洗好的和新购的餐巾之和满足当天的需求量Ri,并使N天总的费用最小。
【输入】
输入文件共3行,第1行为总天数;第2行为每天所需的餐巾块数;第3行为每块餐巾的新购费用p,快洗所需天数m,快洗所需费用f,慢洗所需天数n,慢洗所需费用s。
【输出】
输出文件共n+1行。第1行为最小的费用。下面的n行为从第1天开始每天需要的总餐巾数、需购买的新餐巾数、结束时往快、慢洗部送洗的餐巾数以及用到的来自快洗的餐巾数和来自慢洗的餐巾数。
===========样例来自线性规划与网络流24题====================
【输入文件示例 】
3 10 2 3 3 2
5
6
7
【输出文件示例 】
145
=========以下内容来自http://www.byvoid.com/blog/lpf24-solution/==============
【问题分析】
网络优化问题,用最小费用最大流解决。
【建模方法】
把每天分为二分图两个集合中的顶点Xi,Yi,建立附加源S汇T。
1、从S向每个Xi连一条容量为ri,费用为0的有向边。
2、从每个Yi向T连一条容量为ri,费用为0的有向边。
3、从S向每个Yi连一条容量为无穷大,费用为p的有向边。
4、从每个Xi向Xi+1(i+1<=N)连一条容量为无穷大,费用为0的有向边。
5、从每个Xi向Yi+m(i+m<=N)连一条容量为无穷大,费用为f的有向边。
6、从每个Xi向Yi+n(i+n<=N)连一条容量为无穷大,费用为s的有向边。
求网络最小费用最大流,费用流值就是要求的最小总花费。
【建模分析】
这个问题的主要约束条件是每天的餐巾够用,而餐巾的来源可能是最新购买,也可能是前几天送洗,今天刚刚洗好的餐巾。每天用完的餐巾可以选择送到快洗部或慢洗部,或者留到下一天再处理。
经过分析可以把每天要用的和用完的分离开处理,建模后就是二分图。二分图X集合中顶点Xi表示第i天用完的餐巾,其数量为ri,所以从S向Xi连接容量为ri的边作为限制。Y集合中每个点Yi则是第i天需要的餐巾,数量为ri,与T连接的边容量作为限制。每天用完的餐巾可以选择留到下一天(Xi->Xi+1),不需要花费,送到快洗部(Xi->Yi+m),费用为f,送到慢洗部(Xi->Yi+n),费用为s。每天需要的餐巾除了刚刚洗好的餐巾,还可能是新购买的(S->Yi),费用为p。
在网络上求出的最小费用最大流,满足了问题的约束条件(因为在这个图上最大流一定可以使与T连接的边全部满流,其他边只要有可行流就满足条件),而且还可以保证总费用最小,就是我们的优化目标。
======下面是我的程序==========================================
#include <stdio.h> #define MAXN 10000 #define MAXM 1000000 #define INF 19930317 int c[MAXM]; int e[MAXM]; int cost[MAXM]; int opp[MAXM]; int next[MAXM]; int g[MAXN]; int size; int s, t, n; int dist[MAXN]; int flag[MAXN]; int head, tail; int q[MAXM]; int prev[MAXN]; int pree[MAXN]; void addedge(int u, int v, int flow, int cc) { e[++size] = v; c[size] = flow; opp[size] = size + 1; cost[size] = cc; next[size] = g[u]; g[u] = size; e[++size] = u; c[size] = 0; opp[size] = size - 1; cost[size] = -cc; next[size] = g[v]; g[v] = size; } int min(int a, int b) { return a < b ? a : b; } void init() { int p, sd, fd, sc, fc, i, need; //freopen("napk.in","r",stdin); //freopen("napk.out","w",stdout); scanf("%d%d", &n, &p); scanf("%d%d%d%d", &fd, &fc, &sd, &sc); s = n * 2 + 1; t = s + 1; for (i = 1; i <= n; i++) { scanf("%d", &need); addedge(s, i, need, 0); addedge(n + i, t, need, 0); addedge(s, n + i, INF, p); if (i < n) addedge(i, i + 1, INF, 0); if (i + fd <= n) addedge(i, i + fd + n, INF, fc); if (i + sd <= n) addedge(i, i + sd + n, INF, sc); } } int SPFA() { int i, head, tail, p, v; for (i = 1; i <= t; i++) dist[i] =INF; dist[s] = 0; memset(flag, 0, sizeof(flag)); head = tail = 0; q[++tail] = s; flag[s] = 1; while (head < tail) { v = q[++head]; flag[v] = 0; for (p = g[v]; p; p = next[p]) { i = e[p]; if (c[p] && dist[i] > cost[p] + dist[v]) { dist[i] = cost[p] + dist[v]; pree[i] = p; prev[i] = v; if (!flag[i]) { q[++tail] = i; flag[i] = 1; } } } } return dist[t] != INF; } int costflow() { int now, flow, mincost = 0, p; while (SPFA()) { flow = INF; for (now = t; now != s; now = prev[now]) flow = min(flow, c[pree[now]]); for (now = t; now != s; now = prev[now]) { p = pree[now]; mincost += flow * cost[p]; c[p] -= flow; c[opp[p]] += flow; } } return mincost; } int main() { init(); printf("%d\n", costflow()); }