【题意】
【分析】
这道题目的建图方式真的很奇特
首先我们容易想到S到一类人费用为ci,人到天连边,然后每天到T连ai的边
但是这样显然是不对的
因为没有一一对应,我们要考虑新的建图方式
对于每一中志愿者(si,ti,ci),我们建一条跨过si到ti的所有点的边,费用为ci,来表示“这一个流量一直流完了这些区域”
我们在点(i,i+1)之间建边,设流量为-a[i],也就是负的当天需求数,费用自然是零的
然后,令上文中的志愿者(si,ti,ci),建边(si,ti+1),费用ci,流量无限
此时我们相当于是把第i天的决策放到了第i个点和第i+1个点之间的所有边上(就是把所有点排成一排,这两个点之间的那一条位置里的所有边,包括跨过这个区间的志愿者边)
需要志愿者?让它们从志愿者边上流过去,同时让人数限制边满流到-a[i],这样求一个1-n+1的最大流,流量为0的最小费用就是雇佣人的最小费用了
为了让这个限制起效,又因为网络流中流量非负,所以我们建立点SS和TT,连边(SS,1)(n+1,TT),限制为inf,费用为0
同时,我们把之前的人数限制边的流量改成(inf-a[i]),这样最终的SS-TT最大流一定是inf,而且限制依然成立
摘录自——[NOI2008][bzoj1061] 志愿者招募 [费用流+巧妙的建图]
【代码】
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define mp make_pair #define fi firstP #define se second const int maxn=1e3+5; const int maxm=2e4+5; int n,m,head[maxn]; struct edge { int to,nxt,v,c; }e[maxm<<1]; int tot=1,S,T; void add(int x,int y,int z,int c) { e[++tot].to=y; e[tot].nxt=head[x]; e[tot].v=z; e[tot].c=c; head[x]=tot; e[++tot].to=x; e[tot].nxt=head[y]; e[tot].v=0; e[tot].c=-c; head[y]=tot; } const int inf=0x3f3f3f3f; int dis[maxn],vis[maxn],pre[maxn]; bool spfa() { for(int i=S;i<=T;i++) vis[i]=0,dis[i]=inf; dis[S]=0; queue <int> q; q.push(S); vis[S]=1; while(!q.empty()) { int u=q.front(); q.pop(); for(int i=head[u];i;i=e[i].nxt) { int to=e[i].to; if(dis[to]>dis[u]+e[i].c && e[i].v>0) { dis[to]=dis[u]+e[i].c; pre[to]=i; if(!vis[to]) { q.push(to); vis[to]=1; } } } vis[u]=0; } return (dis[T]!=inf); } int mcmf() { int res=0,cost=0; while(spfa()) { int flow=inf+1; for(int i=T;i!=S;i=e[pre[i]^1].to) flow=min(flow,e[pre[i]].v); for(int i=T;i!=S;i=e[pre[i]^1].to) { e[pre[i]].v-=flow; e[pre[i]^1].v+=flow; cost+=e[pre[i]].c*flow; } } return cost; } int main() { // freopen("a.in","r",stdin); // freopen("a.out","w",stdout); scanf("%d%d",&n,&m); S=0; T=n+2; int x,y,z; for(int i=1;i<=n;i++) { scanf("%d",&x); add(i,i+1,inf-x,0); } add(S,1,inf,0); add(n+1,T,inf,0); for(int i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); add(x,y+1,inf,z); } printf("%d",mcmf()); return 0; }