NOI 2008 志愿者招募 / bzoj 1061 (最小费用最大流)
纠结了很久的一道题目,从拿到题目开始就没有想法, 再到看了几天才看懂了怎么做。 为了看懂题解我还特意去看了看线性规划,这题确实不简单。这建图的思路很值得学习!
关于题解, 网上有大神的详细解题报告,如果想彻底的搞懂建议去看点线性规划基础的东西,不然会对其中的松弛操作,还有不等式的建立搞不清楚。
我的理解,首先看到题目就可以知道这个必定和线性规划有关, 整个就一个线性规划的模型, 然后自然的就会列出不等式,进行松弛操作。 在得到一组等式后, 又可以想到的是, 那种用网络流求解不等式的方法, 当时要满足其中的变量要正负都出现一次, 然后等式两两相减,最后就是建图, 用最小费用流求解了。
1061: [Noi2008]志愿者招募
Time Limit: 20 Sec Memory Limit: 162 MBSubmit: 892 Solved: 561
[Submit][Status][Discuss]
Description
申奥成功后,布布经过不懈努力,终于成为奥组委下属公司人力资源部门的主管。布布刚上任就遇到了一个难题:为即将启动的奥运新项目招募一批短期志愿者。经过估算,这个项目需要N 天才能完成,其中第i 天至少需要Ai 个人。 布布通过了解得知,一共有M 类志愿者可以招募。其中第i 类可以从第Si 天工作到第Ti 天,招募费用是每人Ci 元。新官上任三把火,为了出色地完成自己的工作,布布希望用尽量少的费用招募足够的志愿者,但这并不是他的特长!于是布布找到了你,希望你帮他设计一种最优的招募方案。
Input
第一行包含两个整数N, M,表示完成项目的天数和可以招募的志愿者的种类。 接下来的一行中包含N 个非负整数,表示每天至少需要的志愿者人数。 接下来的M 行中每行包含三个整数Si, Ti, Ci,含义如上文所述。为了方便起见,我们可以认为每类志愿者的数量都是无限多的。
Output
仅包含一个整数,表示你所设计的最优方案的总费用。
Sample Input
3 3
2 3 4
1 2 2
2 3 5
3 3 2
2 3 4
1 2 2
2 3 5
3 3 2
Sample Output
14
HINT
招募第一类志愿者3名,第三类志愿者4名
30%的数据中,1 ≤ N, M ≤ 10,1 ≤ Ai ≤ 10;
100%的数据中,1 ≤ N ≤ 1000,1 ≤ M ≤ 10000,题目中其他所涉及的数据均
不超过2^31-1。
Source
#include <stdio.h> #include <string.h> #include <iostream> using namespace std; #define N 1100 #define M 1000010 #define INF 0x3fffffff struct node { int to,next,w,c; }edge[M]; int n,m; int save[N]; int cnt,pre[N]; int s,t; int point[N],pedge[N]; int que[2*M]; void add_edge(int u,int v,int w,int c) { edge[cnt].to=v; edge[cnt].w=w; edge[cnt].c=c; edge[cnt].next=pre[u]; pre[u]=cnt++; } void init() { cnt=0; memset(pre,-1,sizeof(pre)); memset(save,0,sizeof(save)); for(int i=1;i<=n;i++) scanf("%d",&save[i]); for(int i=0;i<m;i++) { int x,y,key; scanf("%d%d%d",&x,&y,&key); add_edge(x,y+1,INF,key); add_edge(y+1,x,0,-key); } s=0; t=n+2; for(int i=1;i<=n+1;i++) { int tmp=save[i]-save[i-1]; if(tmp>0) { add_edge(s,i,tmp,0); add_edge(i,s,0,0); } else { add_edge(i,t,-tmp,0); add_edge(t,i,0,0); } if(i!=1) { add_edge(i,i-1,INF,0); add_edge(i-1,i,0,0); } } }// 建好了图,然后就是最小费用最大流了... int SPFA() { int dis[N],mark[N],cnt[N]; int qf=1,qd=0,flag=0; memset(cnt,0,sizeof(cnt)); memset(point,-1,sizeof(point)); memset(pedge,-1,sizeof(pedge)); // 这里因为边没有记录上一个边所以要记录点和边 for(int i=0;i<N;i++) { dis[i]=INF; mark[i]=0; } mark[0]=1; dis[0]=0; que[0]=s; cnt[0]++; while(qf>qd) { int cur=que[qd++]; mark[cur]=0; cnt[cur]++; if(cnt[cur] > n+2) { flag=1; break; } for(int p=pre[cur];p!=-1;p=edge[p].next) { if(edge[p].w==0) continue; int v=edge[p].to,w=edge[p].c; if( dis[v] > dis[cur]+w ) { point[v]=cur; pedge[v]=p; dis[v]=dis[cur]+w; if( mark[v]==0 ) { mark[v]=1; que[qf++]=v; } } } } if(dis[t]==INF||flag==1) return 0; else return 1; } int cal() { int mi=INF; int tmp=t; while(tmp!=s) { if(edge[pedge[tmp]].w<mi) mi=edge[pedge[tmp]].w; tmp=point[tmp]; } int sum=0; tmp=t; while(tmp!=s) { int p = pedge[tmp]; edge[p].w -= mi; edge[p^1].w += mi; sum += mi*edge[p].c; tmp = point[tmp]; } return sum; } int main() { while(scanf("%d%d",&n,&m)!=EOF) { init(); int sum=0; while(SPFA()) { sum+=cal(); } printf("%d\n",sum); } return 0; }