整理的算法模板合集: ACM模板
B、太空飞行计划问题(最大权闭合图转最小割、最小割方案输出)【省选/NOI- 】
【问题分析】
最大权闭合图问题,可以转化成最小割问题,进而用最大流解决。
【建模方法】
把每个实验看作二分图X集合中的顶点,每个设备看作二分图Y集合中的顶点,增加源S和汇T。
1、从S向每个Xi连接一条容量为该点收入的有向边。
2、从Yi向T连接一条容量为该点支出的有向边。
3、如果一个实验i需要设备j,连接一条从Xi到Yj容量为无穷大的有向边。
统计出所有实验的收入只和Total,求网络最大流Maxflow,最大收益就是Total-Maxflow。对应的解就是最小割划分出的S集合中的点,也就是最后一次增广找到阻塞流时能从S访问到的顶点。
【建模分析】
定义一个割划分出的S集合为一个解,那么割集的容量之和就是(未被选的A集合中的顶点的权值 + 被选的B集合中的顶点的权值),记为Cut。A集合中所有顶点的权值之和记为Total,那么Total - Cut就是(被选的A集合中的顶点的权值 - 被选的B集合中的顶点的权值),即为我们的目标函数,记为A。要想最大化目标函数A,就要尽可能使Cut小,Total是固定值,所以目标函数A取得最大值的时候,Cut最小,即为最小割。
该问题的一般模型为最大权闭合图,相关讨论见《最小割模型在信息学竞赛中的应用》作者胡伯涛。
【反思与总结】
割掉的实验仪器就是用到的实验仪器,割掉的实验就是不做的实验,所以我们所有割掉的就都是花费,我们用总预计收益减去最小花费就得到了最大收益。
对于所有从源点到汇点的路,我们要么割掉实验,表示不做这个实验,要么割掉实验仪器,表示买这个仪器,那么我们所有需要这个仪器的实验(与之相连的实验)就可以做了。
由于实验及器材之间的边是不可割的,因此a(要用的实验器材)一定在A(要做的实验)的点集中;而b(不要用的实验器材)与A(要做的实验)间并无连边,因此无法通过A(要做的实验)与源点联通,就只能与B(所有不做的实验)属于同一集合啦!
我们这样给边赋值,对于每个割,用所有实验的奖金( w A + w B w_A+w_B wA+wB )减去该割( w B + w a w_B+w_a wB+wa )就是该方案的答案( w A − w a w_A-w_a wA−wa ),最大化答案就是最小化割值。
最后的输出方案:
用Dninc求最大流的好处是便于我们输出,因为层数如果为INF,那么显然该边容量大于0,这就说明这条边并没有流量流过,那么显然与它相连的实验或者器材没有被使用(因为使用过的话,边的容量会变为0),直接输出即可。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long ll;
const int N = 500007, M = 5000007, INF = 0x3f3f3f3f;
inline int read(int &x)
{
char c;x = 0;
while(c < '0' || c > '9')c = getchar();
while(c >= '0' && c <= '9'){
x = x * 10 + c - '0';c = getchar();}
return c == '\r' || c == '\n' ? 0 : 1;
}
int n, m;
int head[N], ver[M], nex[M], tot;
int deep[N];
int cur[N], S, T;
ll maxflow, edge[M];
void add(int x, int y, int z, bool o = 1){
ver[tot] = y;
edge[tot] = z;
nex[tot] = head[x];
head[x] = tot ++ ;
if(o)add(y, x, 0, 0);
}
bool bfs()
{
memset(deep, 0x3f, sizeof deep);
queue<int>q;
q.push(S), deep[S] = 0, cur[S] = head[S];
while(q.size())
{
int x = q.front();
q.pop();
for(int i = head[x]; ~i; i = nex[i]){
int y = ver[i], z = edge[i];
if(z > 0 && deep[y] == INF){
q.push(y);
deep[y] = deep[x] + 1;
if(y == T)return true;
}
}
}
return false;
}
ll dfs(int x, ll flow)
{
if(x == T)return flow;
ll ans = 0, i, k;
for(i = cur[x]; ~i && flow; i = nex[i]){
cur[x] = i;
int y = ver[i];
ll z = edge[i];
if(edge[i] > 0 && (deep[y] == deep[x] + 1))
{
k = dfs(y, min(flow, z));
if(!k)deep[y] = 0;//待定
edge[i] -= k;
edge[i ^ 1] += k;
ans += k;
flow -= k;
}
}
if(!ans)deep[x] = INF;//流量不为零,说明不是最小割
return ans;
}
void dinic()
{
while(bfs()){
for(int i = 1; i <= n + m + 1; ++ i)
cur[i] = head[i];
maxflow += dfs(S, INF);
}
}
int main()
{
memset(head, -1, sizeof head);
scanf("%d%d", &n, &m);
S = 0, T = n + m + 1;
ll sum = 0;
for(int i = 1; i <= n; ++ i){
int x, ch;
ch = read(x);
sum += x;
add(S, i, x);
while(ch){
ch = read(x);
add(i, x + n, INF);
}
}
for(int i = 1; i <= m; ++ i){
int x;
scanf("%d", &x);
add(i + n, T, x);
}
dinic();
ll ans = sum - maxflow;
for(int i = 1; i <= n; ++ i)
if(deep[i] != INF)printf("%d ", i);
puts("");
for(int i = 1; i <= m; ++ i)
if(deep[n + i] != INF)printf("%d ", i);
puts("");
printf("%lld\n", ans);
return 0;
}