P3980 [NOI2008] 志愿者招募
P3980 [NOI2008] 志愿者招募
不会写线性规划,所以我写的最小费用最大流
神仙建模题
首先,我们建一条
和一条
这样就保证了我们这张图的最大流为
然后考虑刻画题目:
首先将每一天
表示这个点这一天需要
然后我们考虑如何聘请志愿者:
先说如何建边:对于每种边(l,r,w),建一条(l,r+1,w,inf)
这样一来我们上面刻画(i,i+1,0,inf-a[i]),表示第i天需要a[i]的正确性就得到了证明:
显然,要想在这个图中跑出最大流,那么对于n+1时的流量就应该大于等于inf。
我们来看看我们的算法做了什么:首先,在第一次找增广路时,S->T的流量应该是inf-max(a[i]);
显然,这对于希望跑满inf的T来说时不够的,那么之后我们通过购买的方式从一些点在一些区间内买到了max(a[i])或者这个点本身就还剩这些
所以我们对第一次找完增广路的图理解可能比较容易:
第一次跑完增广路后,每个点的剩余流量即为自己所需志愿者数与全局志愿者需求最大数的差
那么我们在将最大流跑到inf的这一段时间中,所做的事就是不断的增广S->T的路径直到T的入流量为inf
Code:
#include<bits/stdc++.h> using namespace std; const int N=1005; const int M=10005; const int inf=1e9; int head[N],arc[N],dis[N],dl[N],a[N]; int n,m,S,T,e_cnt=1,ans; struct Edge{ int to,nxt,w,flow; }e[M<<2]; void add(int x,int y,int w,int flow) { e[++e_cnt]={y,head[x],w,flow};head[x]=e_cnt; e[++e_cnt]={x,head[y],-w,0};head[y]=e_cnt; } void init() { for(int i=0;i<N;i++) { dis[i]=inf; arc[i]=head[i]; } } queue<int> Q; bool spfa(int s,int t) { init(); dis[s]=0;dl[s]=1; Q.push(s); while(!Q.empty()) { int u=Q.front();Q.pop();dl[u]=0; for(int i=head[u];i;i=e[i].nxt) { int v=e[i].to,w=e[i].w; if(dis[v]>dis[u]+w&&e[i].flow>0) { dis[v]=dis[u]+w; if(!dl[v]) { dl[v]=1; Q.push(v); } } } } return dis[t]!=inf; } int dfs(int u,int t,int in) { if(u==t)return in; int out=0; dl[u]=1; for(int &i=arc[u];i&∈i=e[i].nxt) { int v=e[i].to,w=e[i].w; if(dis[v]==dis[u]+w&&e[i].flow>0&&!dl[v]) { int fl=dfs(v,t,min(e[i].flow,in)); e[i].flow-=fl,e[i^1].flow+=fl; in-=fl;out+=fl; } } dl[u]=0; return out; } void dinic(int s,int t) { while(spfa(s,t)) { int fl=dfs(s,t,inf); ans+=fl*dis[t]; } printf("%d\n",ans); } void solve() { cin>>n>>m; S=0,T=n+2; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); add(i,i+1,0,inf-a[i]); } add(S,1,0,inf); add(n+1,T,0,inf); for(int i=1,x,y,w;i<=m;i++) { scanf("%d%d%d",&x,&y,&w); add(x,y+1,w,inf); } dinic(S,T); } int main() { //freopen("P3980.in","r",stdin);//freopen("P3980.out","w",stdout); solve(); return 0; }