【bzoj1391】[Ceoi2008]order 网络流最小割
原文地址:http://www.cnblogs.com/GXZlegend/p/6796937.html
题目描述
有N个工作,M种机器,每种机器你可以租或者买过来. 每个工作包括若干道工序,每道工序需要某种机器来完成,你可以通过购买或租用机器来完成。 现在给出这些参数,求最大利润
输入
第一行给出 N,M(1<=N<=1200,1<=M<=1200) 下面将有N块数据,每块数据第一行给出完成这个任务能赚到的钱(其在[1,5000])及有多少道工序 接下来若干行每行两个数,分别描述完成工序所需要的机器编号及租用它的费用(其在[1,20000]) 最后M行,每行给出购买机器的费用(其在[1,20000])
输出
最大利润
样例输入
2 3
100 2
1 30
2 20
100 2
1 40
3 80
50
80
110
样例输出
50
题解
最小割,类似最大权闭合图
首先考虑如果不能租用机器的话,那么就是一个最大权闭合图的水题,S向工作连容量为利润的边,工作向机器连容量为inf的边,机器向T连容量为费用的边,答案为sum-mincut。
那么加上了租用机器该怎么办呢?其实是类似的。
最大权闭合图求法中边容量为inf,目的是防止割断该边。本题中想割断工作与机器之间的边,则边容量不为inf,而是租用机器的费用。
这与最大权闭合图求法的证明差不多,自己画一下推导推导就行了。
此题需要加弧优化。
#include <cstdio> #include <cstring> #include <queue> using namespace std; queue<int> q; int head[3000] , to[4000000] , val[4000000] , next[4000000] , cnt = 1 , cur[4000000] , s , t , dis[3000]; void add(int x , int y , int z) { to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt; to[++cnt] = x , val[cnt] = 0 , next[cnt] = head[y] , head[y] = cnt; } bool bfs() { int x , i; while(!q.empty()) q.pop(); memset(dis , 0 , sizeof(dis)); dis[s] = 1 , q.push(s); while(!q.empty()) { x = q.front() , q.pop(); for(i = head[x] ; i ; i = next[i]) if(val[i] && !dis[to[i]]) dis[to[i]] = dis[x] + 1 , q.push(to[i]); } return dis[t] > 0; } int dinic(int x , int low) { if(x == t) return low; int temp = low , i , k; for(i = cur[x] ; i ; i = next[i]) { if(val[i] && dis[to[i]] == dis[x] + 1) { k = dinic(to[i] , min(temp , val[i])); if(!k) dis[to[i]] = 0; val[i] -= k; if(val[i]) cur[x] = i; val[i ^ 1] += k; if(!(temp -= k)) break; } } return low - temp; } int main() { int n , m , i , a , k , p , c , sum = 0; scanf("%d%d" , &n , &m); s = 0 , t = n + m + 1; for(i = 1 ; i <= n ; i ++ ) { scanf("%d%d" , &a , &k) , add(s , i , a) , sum += a; while(k -- ) scanf("%d%d" , &p , &c) , add(i , p + n , c); } for(i = 1 ; i <= m ; i ++ ) scanf("%d" , &a) , add(i + n , t , a); while(bfs()) { for(i = s ; i <= t ; i ++ ) cur[i] = head[i]; sum -= dinic(s , 0x7fffffff); } printf("%d\n" , sum); return 0; }