bzoj1061: [Noi2008]志愿者招募
费用流。
传说中建模之集大成之题。。。
https://www.byvoid.com/blog/noi-2008-employee 题解
题解里面关于建模已经讲的很全了。
我根据自己的理解写写建模的实际意义。
如果a[i]-a[i-1]>=0,代表需要新雇佣人。否则就代表有人离开了。(离开并不一定要在t[i]+1天,如果后面人多余的话,很早就会离开)。
1.建模为S->i容量为 a[i]-a[i-1],费用为0. 2.i->T 容量为 –(a[i]-a[i-1]),费用为0.
对于每个人来说他会在s[i]天进入,在(t[i]+1)天或更早离开。
3.在s[i]到t[i]+1连一条容量无限费用为c[i]的边。4.在(i+1)->i连一条容量无限费用为0的边。
S代表进入的人,T代表着离开的人。
最重要的是建模时的流量平衡思想。
设Y[i]为第i天多余的人。X[i]为第i种志愿者的数量。
对于每个点 (a[i]-a[i-1])+(Y[i]-Y[i-1])+sum(X[k1])-sum(X[k2])=0 (k1和k2满足t[k1]+1=i,s[k2]=i).
每个点都满足这样的流量平衡
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; typedef long long LL; const int maxn = 1000 + 10; const int maxm = 100000 + 10; const int inf = 0x3f3f3f3f; int g[maxn],v[maxm],f[maxm],c[maxm],next[maxm],eid; int n,m,S,T; int a[maxn]; long long res; LL dist[maxn]; int pre[maxn]; bool inque[maxn]; int q[maxm],l,r,u; void addedge(int a,int b,int F,int C) { v[eid]=b; f[eid]=F; c[eid]=C; next[eid]=g[a]; g[a]=eid++; v[eid]=a; f[eid]=0; c[eid]=-C; next[eid]=g[b]; g[b]=eid++; } void build() { memset(g,-1,sizeof(g)); scanf("%d%d",&n,&m); S=0; T=n+2; for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1,d;i<=n+1;i++) { d=a[i]-a[i-1]; if(d>=0) addedge(S,i,d,0); else addedge(i,T,-d,0); } for(int i=1,s,t,c;i<=m;i++) { scanf("%d%d%d",&s,&t,&c); addedge(s,t+1,inf,c); } for(int i=1;i<=n;i++) addedge(i+1,i,inf,0); } bool SPFA() { memset(dist,0x3f,sizeof(dist)); l=r=0; q[r++]=S; dist[S]=0; while(l<r) { inque[u=q[l++]]=0; for(int i=g[u];~i;i=next[i]) if(f[i] && dist[v[i]]>dist[u]+c[i]) { dist[v[i]]=dist[u]+c[i]; pre[v[i]]=i; if(!inque[v[i]]) inque[q[r++]=v[i]]=1; } } return dist[T]<inf; } long long augment() { long long aug=inf,res=0; for(int i=T;i!=S;i=v[pre[i]^1]) aug=min(aug,(long long)f[pre[i]]); for(int i=T;i!=S;i=v[pre[i]^1]) { f[pre[i]]-=aug; f[pre[i]^1]+=aug; res+=aug*c[pre[i]]; } return res; } void solve() { long long res=0; while(SPFA()) res+=augment(); printf("%lld\n",res); } int main() { build(); solve(); return 0; }