【BZOJ1391】Order(CEOI2008)-最小割
测试地址:Order
题目大意:有项任务和种机器,每项任务都有若干道工序,每道工序需要使用一种机器,完成第项任务的所有工序就能拿到的报酬。对于机器可以租用或者购买,租用时只能用于某一项任务的某一道工序,用于不同位置时租金可能不同。而购买是一次性的,只要买下来,所有用到这台机器加工的工序都能做,而只需付一次的钱。现在给你这些信息,问能得到的最大利润是多少。。
做法:本题需要用到最小割。
我们发现直接求最大利润比较困难,所以我们把所有任务的报酬之和看做预期的利润,求最小的损失,也就是成本。
我们知道,损失会产生于三个方面:一是某项任务无法完成,二是在某道工序租用某种机器,三是购买某种机器。于是我们建图,从源点到每项任务连一条容量为报酬的边,从每种机器到汇点连一条容量为价钱的边,从每项任务到各工序所需的机器连一条容量为租金的边。因为我们知道,如果要完成一项任务,我们必须租用或者购买它所需的全部机器,这就等价于从这项任务到汇点不存在路径,否则我们就完不成这项任务,等价于割掉源点到这项任务的边。因此我们意识到要求一个最小割,我们就完成了这一题。注意要加当前弧优化,不然会TLE。
这个东西很像最大权闭合子图的模型,只不过有了租金这一概念,所以原图中的边容量不应为正无穷。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
const int inf=1000000000;
int n,m,sum=0,S,T;
int first[3010]={0},tot=1,cur[3010],lvl[3010];
int h,t,q[3010];
struct edge
{
int v,next,f;
}e[3000010];
void insert(int a,int b,int f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}
void init()
{
scanf("%d%d",&n,&m);
S=n+m+1,T=n+m+2;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
sum+=x;
insert(S,i,x);
for(int j=1;j<=y;j++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(i,n+a,b);
}
}
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
insert(n+i,T,x);
}
}
bool makelevel()
{
for(int i=1;i<=T;i++)
{
lvl[i]=-1;
cur[i]=first[i];
}
lvl[S]=0;
h=t=1;
q[1]=S;
while(h<=t)
{
int v=q[h++];
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
q[++t]=e[i].v;
}
}
return lvl[T]!=-1;
}
int maxflow(int v,int maxf)
{
if (v==T) return maxf;
int ret=0,f;
for(int i=cur[v];i;i=e[i].next)
{
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(maxf-ret,e[i].f));
ret+=f;
e[i].f-=f;
e[i^1].f+=f;
if (ret==maxf) break;
}
cur[v]=i;
}
if (!ret) lvl[v]=-1;
return ret;
}
void dinic()
{
int maxf=0;
while(makelevel()) maxf+=maxflow(S,inf);
printf("%d",sum-maxf);
}
int main()
{
init();
dinic();
return 0;
}