BZOJ1061 - [NOI2008]志愿者招募
Description
实行一个\(n(n\leq10^3)\)天的项目,其中第\(i\)天至少需要\(b_i\)个人。一共有\(m(m\leq10^4)\)类志愿者可以招募,其中第\(i\)类可以从第\(s_i\)天工作到第\(t_i\)天,招募费用是每人\(c_i\)元。求最小花费的招募方案。
Solution1
根据题意,很容易列出一个线性规划:
\[\begin{align}
min \quad & \sum_{i=1}^m c_ix_i \\
s.t. \quad & \sum_{i=1}^m a_{ji}x_i \geq b_j \\
& x_i\geq0
\end{align}
$$其中$x_i$代表第$i$类志愿者的个数,$a_{ji}=1$当且仅当$s_i\leq j \leq t_i$。
该线性规划可转化为其对偶问题:
\]
\begin{align}
max \quad & \sum_{j=1}^n b_jy_j \
s.t. \quad & \sum_{j=1}^n a_{ji}y_j \leq c_i \
& y_j\geq0
\end{align}
\[用单纯形求解即可。
##Solution2
还可以用费用流求解。按如下方式建图:
- $s=0,t=n+1$,共$n+2$个点;
- 连边$(s,1)$,容量为足够大的数$U$,费用为$0$;
- 连边$(i,i+1)$,容量为$U-b_i$,费用为$0$;
- 对于第$i$类志愿者,连边$(s_i,t_i+1)$,容量为$+\infty$,费用为$c_i$。
第$i$类志愿者代表的边每有$1$的流量,原图$s_i$到$t_i$就可以少$1$的流量,就相当于招募了一个志愿者。
##Code
```cpp
//[Noi2008]志愿者招募
#include <cstdio>
inline char gc()
{
static char now[1<<16],*S,*T;
if(S==T) {T=(S=now)+fread(now,1,1<<16,stdin); if(S==T) return EOF;}
return *S++;
}
inline int read()
{
int x=0; char ch=gc();
while(ch<'0'||'9'<ch) ch=gc();
while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
return x;
}
int const N=1e3+10;
double const EPS=1e-8;
bool equal0(double x) {return -EPS<x&&x<EPS;}
int n,m; double a[N*10][N];
void pivot(int x,int y)
{
double t=a[x][y]; a[x][y]=1;
for(int i=0;i<=n;i++) a[x][i]/=t;
for(int i=0;i<=m;i++)
{
if(i==x||equal0(a[i][y])) continue;
t=a[i][y]; a[i][y]=0;
for(int j=0;j<=n;j++) a[i][j]-=a[x][j]*t;
}
}
void simplex()
{
while(true)
{
int x=0,y=0; double minX=1e18;
for(int i=1;!y&&i<=n;i++) if(a[0][i]>EPS) y=i;
if(!y) return;
for(int i=1;i<=m;i++)
if(a[i][y]>EPS&&a[i][0]/a[i][y]<minX) minX=a[i][0]/a[i][y],x=i;
if(!x) return;
pivot(x,y);
}
}
int main()
{
n=read(),m=read();
for(int i=1;i<=n;++i) a[0][i]=read();
for(int i=1;i<=m;++i)
{
int x=read(),y=read();
for(int j=x;j<=y;++j) a[i][j]=1;
a[i][0]=read();
}
simplex();
printf("%lld\n",-(long long)a[0][0]);
return 0;
}
```
```cpp
//[Noi2008]志愿者招募
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long lint;
inline char gc()
{
static char now[1<<16],*S,*T;
if(S==T) {T=(S=now)+fread(now,1,1<<16,stdin); if(S==T) return EOF;}
return *S++;
}
inline int read()
{
int x=0; char ch=gc();
while(ch<'0'||'9'<ch) ch=gc();
while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
return x;
}
int const N=1e3+10;
int const INF=0x7FFFFFFF;
int n,m;
int s,t;
int h[N],cnt;
struct edge{int v,c,w,nxt;} ed[N*30];
void edAdd(int u,int v,int c,int w)
{
cnt++; ed[cnt].v=v,ed[cnt].c=c,ed[cnt].w=w,ed[cnt].nxt=h[u],h[u]=cnt;
cnt++; ed[cnt].v=u,ed[cnt].c=0,ed[cnt].w=-w,ed[cnt].nxt=h[v],h[v]=cnt;
}
int pre[N],dst[N];
queue<int> Q; bool inQ[N];
bool SPFA()
{
for(int i=s;i<=t;i++) dst[i]=INF,pre[i]=0;
dst[s]=0; Q.push(s),inQ[s]=true;
while(!Q.empty())
{
int u=Q.front(); Q.pop(),inQ[u]=false;
for(int i=h[u];i;i=ed[i].nxt)
{
int v=ed[i].v,w=ed[i].w;
if(ed[i].c&&dst[u]+w<dst[v])
{
dst[v]=dst[u]+w,pre[v]=i;
if(!inQ[v]) Q.push(v),inQ[v]=true;
}
}
}
return dst[t]<INF;
}
lint netFlow()
{
lint cost=0;
while(SPFA())
{
int fl=INF;
for(int i=pre[t];i;i=pre[ed[i^1].v]) fl=min(fl,ed[i].c);
for(int i=pre[t];i;i=pre[ed[i^1].v]) ed[i].c-=fl,ed[i^1].c+=fl;
cost+=(lint)fl*dst[t];
}
return cost;
}
int main()
{
n=read(),m=read();
s=0,t=n+1; cnt=1;
edAdd(s,1,INF,0);
for(int i=1;i<=n;i++) edAdd(i,i+1,INF-read(),0);
for(int i=1;i<=n;i++)
{
int s=read(),t=read(),w=read();
edAdd(s,t+1,INF,w);
}
printf("%lld\n",netFlow());
return 0;
}
```
##P.S.
感觉对偶问题不大好理解呀...\]