NOI2008志愿者招募

题目解答

题目可以使用费用流建模 (费用流得实现细节就不说了 ),下面是一个常用的建模方式:首先按照天数从源到汇依次建立 S->1->2……N->N+1->T。也就是多加入一个点。他们的费用均为0,如图所示,第N天的流量就是 (MAX-第 N天的需求量 )。然后如果有一种雇员从 (x到 y)可以服务,那么连接 (x到 y+1),流量是 MAX,费用为雇佣花费。这样建模的大致思路大家应该已经明白:按照时间发展顺序,顺流而下。当我们选用一个雇员时,若覆盖 [x,y]天,那么自然就是连边 [x,y+1]只有这样才能保证当前的流量可以允许这些天都得到服务。那么为什么对呢?首先最大流一定是 MAX.因为这是最小割,而任何割集都不会小于这个割,所以就拿第一天来说,如果第一天最后求得的流量恰好是: (MAX-N[1]),一定有N[1] 的流量通过别的途径走了,而且对于第一天这个节点的最大流不可能超过 (MAX-N[1]),也就是说别的途径至少会有 N[1]个流量,而别的流量就是延伸出的包含费用的流,就是雇佣花费。也就符合了题目要求:第 1天的雇佣下限是 N[1].由此限制流量来满足题目要求。这样的方法利用的是补集的思想。

费用流模型

运行情况

NOI 2008 - 志愿者招募

程序清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include<iostream>
#include<cstring>
#define BIG 40005
#define MAX 10000000
using namespace std;
struct edge{
long x,y,cap,cost;
edge *next,*target;
edge(){}
edge(int x,int y,int c,int co,edge *n):x(x),y(y),cap(c),cost(co),next(n),target(0){}
void *operator new(size_t,void *ptr){return ptr;}
}*EDGE[BIG];

long source=BIG-2,sink=BIG-1,queue[BIG],lev[BIG],dist[BIG],MAXTOT,n,m,num;
edge *Stack[BIG],*Path[BIG];
bool qin[BIG];
inline bool FindMincost(){
int tail=1,head=0,now=0;
memset(qin,0,sizeof(qin));
memset(lev,-1,sizeof(lev));
for(int i=1;i<=n+1;i++) dist[i]=MAX;
dist[sink]=MAX;
lev[source]=0;
dist[source]=0;
queue[tail]=source;
qin[source]=1;
while(head<tail){
now=queue[++head];
qin[now]=0;
for(edge *e=EDGE[now];e;e=e->next)
if(e->cap!=0 && dist[now]+e->cost<dist[e->y]){
dist[e->y]=dist[now]+e->cost;
lev[e->y]=lev[now]+1;
if(!qin[e->y]) {queue[++tail]=e->y;qin[e->y]=1;}
}
}
return (dist[sink]!=MAX);
}
inline void FindMaxFlow(){
long Lpath=1,record,delta,top=source;
memcpy(Stack,EDGE,sizeof(EDGE));
while(top>0){
if(sink==top){
delta=MAX;
for(int i=1;i<Lpath;i++)
if(delta>Path[i]->cap){
delta=Path[i]->cap;
record=i;
}
for(int i=1;i<Lpath;i++){
Path[i]->cap-=delta;
Path[i]->target->cap+=delta;
MAXTOT+=Path[i]->cost*delta;
}
Lpath=record;
top=Path[Lpath]->x;
}
edge *temE;
for(temE=Stack[top];temE;temE=temE->next)
if(temE->cap!=0 && lev[top]+1==lev[temE->y] && dist[top]+temE->cost==dist[temE->y]) break;
Stack[top]=temE;
if(temE){
Path[Lpath++]=temE;
top=temE->y;
}else{
lev[top]=-1;
dist[top]=MAX;
if(Lpath==1) return ;
top=Path[--Lpath]->x;
}
}
}
inline void BuildEdge(int x,int y,int cap,int cost){
edge *tempEDGE=new edge[3];
EDGE[x]=new((void *)tempEDGE++) edge(x,y,cap,cost,EDGE[x]);
EDGE[y]=new((void *)tempEDGE++) edge(y,x,0,-cost,EDGE[y]);
EDGE[x]->target=EDGE[y];
EDGE[y]->target=EDGE[x];
}
int main(){
cin>>n>>m;
BuildEdge(source,1,MAX,0);
BuildEdge(n+1,sink,MAX,0);
for(int i=1;i<=n;i++){
cin>>num;
BuildEdge(i,i+1,MAX-num,0);
}
long x,y;
for(int i=1;i<=m;i++){
cin>>x>>y>>num;
BuildEdge(x,y+1,MAX,num);
}
while(FindMincost())FindMaxFlow();
cout<< MAXTOT << endl;
return 0;
}

注意事项

MAX的下界:Sigma(需求量[i]) (1<=i<=n); 就是每天的需求量之和。

难易等级

Hard down(NOI)