您的位置:首页 > 其它

LOJ 6001 太空飞行计划 (最大权闭合图+打印最小割)

2017-07-31 17:52 260 查看




题目描述

W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合 E={E1,E2,⋯,Em}
E = \{ E_1, E_2, \cdots, E_m \}E={E​1​​,E​2​​,⋯,E​m​​},和进行这些实验需要使用的全部仪器的集合 I={I1,I2,⋯,In}
I = \{ I_1, I_2, \cdots, I_n \}I={I​1​​,I​2​​,⋯,I​n​​}。实验 Ej
E_jE​j​​ 需要用到的仪器是 I
II 的子集 Rj⊆I
R_j \subseteq IR​j​​⊆I。
配置仪器 Ik
I_kI​k​​ 的费用为 ck
c_kc​k​​ 美元。实验 Ej
E_jE​j​​ 的赞助商已同意为该实验结果支付 pj
p_jp​j​​ 美元。W
教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。


输入格式

第 1
11 行有 2
22 个正整数 m
mm 和 n
nn。m
mm 是实验数,n
nn 是仪器数。接下来的 m
mm 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的 n
nn 个数是配置每个仪器的费用。


输出格式

第 1
11 行是实验编号,第 2
22 行是仪器编号,最后一行是净收益。


样例


样例输入

2 3
10 1 2
25 2 3
5 6 7



样例输出

1 2
1 2 3
17



数据范围与提示

1≤n,m≤50
1 \leq n, m \leq 501≤n,m≤50

题目大意:

有n个实验要做,做掉奖励相应的钱,但是每个实验需要相应的仪器,每个仪器启动起来要相应的钱,安排使得赚钱最多。

 

首先引入一个闭合图的概念。

闭合图就是原图的一个子图,如果一个点u在这个子图内,那么它连出去的所有点v也要在这个子图内。

最大权闭合图就是点的权值和最大的闭合图。

 

模型分析:

1.很明显这是一个二分图,每个实验向需要的仪器连有向边,实验的点权为正,仪器的点权为负,要求最大权闭合图。

2.这是一个选或不选的问题,所以可以转化成最小割的模型,把选的归为S集,不选的归为T集。但是要求获利最大,最小割是最小,所以我们要换个角度,要求扣的钱最少,因为所有实验的前都加起来是一定的。

3.在最小割中,如果把S到所有试验表示的点连一条容量为奖励的钱(A类弧),所有仪器到T连一条容量为启动仪器的钱(b类弧),如果把A类弧割掉了,那么对应的那个实验就归到了T集,也就是不做了,那么就会有损失。如果把B类弧割掉了,那么相应的那个仪器归到了S集,也就是有损失。所有最小割就是使得损失最少的方案。

 

构图方法:

1.增加源点S和汇点T。

2.从S到所有实验连一条边,容量为其获利,从所有仪器到T连一条边,容量为其花费。

3.从每个实验到相应的仪器连容量为inf的边.

 

用上述方法实际上只能过10/12的点,因为没有特判定,默认是做的实验越多越好.也就是说有多个最小割的时候,尽可能少割A类弧。所以我们可以把所有弧的容量都乘以一个较大的数,然后让A类弧的容量都+1。这样求出的最小割一定是原图的最小割,并且尽可能少割A类弧. 经过测试通过了所有测试点。

 

总结:

1.最大权闭合图的通用解法:S到正权值的点连边,容量为其权值,负权值的点到T连边,容量为其绝对值,然后原图中的边容量为inf,ans=所有正权和-最小割。具体证明可以参考胡伯涛的论文。

写程序时,为了重复利用bfs(),我是从T点开始BFS找出集合V-S的,剩下的便是S集了.

因为最后如果x,y分在两个集合,则g(xy)<=0,所以会发现,如果y在V-S中,且g(xy)>0则x必也在V-S中.
即与直接找S集和bfs求各点所在层的过程,均是类似一样的.

 

PS

这里显然可以上有向边.

话说虽说这里去掉源汇就是二分图,但是也不可以找到所有g[S][i]>0的,实验点i,再由i找到所有使g[i][j]>0的,设备点j.然后直接输出找到的所有i和j.

这样会少. 因为存在某些点加入S是由于f(yx)>0,即某些点可能是由于某些设备点而加入的!

此题要求输出方案。Dinic
算法结束(残量网络中不存在 ss 到 tt 的路径)时,残量网络中从 ss 出发可达的所有点的集合是一个最小割的 SS 集合,所以只需输出
bfs 过程达到了的实验和仪器点的编号即可。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
using namespace std;
const int INF = 1e9;
const int maxn = 105;
const int maxv = 2e4;
int head[maxv], cur[maxv], d[maxv], s, t, k, sum;
int n, m;
struct node
{
int v, w, next;
}edge[maxv+6*maxn];
void addEdge(int u, int v, int w)
{
edge[k].v = v;
edge[k].w = w;
edge[k].next = head[u];
head[u] = k++;
edge[k].v = u;
edge[k].w = 0;
edge[k].next = head[v];
head[v] = k++;

}
int bfs()
{
memset(d, 0, sizeof(d));
d[s] = 1;
queue<int> q;
q.push(s);
while(!q.empty())
{
int u = q.front();
if(u == t) return 1;
q.pop();
for(int i = head[u]; i != -1; i = edge[i].next)
{
int to = edge[i].v, w = edge[i].w;
if(w && d[to] == 0)
{
d[to] = d[u] + 1;
if(to == t) return 1;
q.push(to);
}
}
}
return 0;
}
int dfs(int u, int maxflow)
{
if(u == t) return maxflow;
int ret = 0;
for(int i = cur[u]; i != -1; i = edge[i].next)
{
int to = edge[i].v, w = edge[i].w;
if(w && d[to] == d[u]+1)
{
int f = dfs(to, min(maxflow-ret, w));
edge[i].w -= f;
edge[i^1].w += f;
ret += f;
if(ret == maxflow) return ret;
}
}
return ret;
}
int Dinic()
{
int ans = 0;
while(bfs() == 1)
{
memcpy(cur, head, sizeof(head));
ans += dfs(s, INF);
}
return ans;
}
char str[1000];
int main()
{
while(~scanf("%d%d", &m, &n))
{
k = 0; s = 0; t = n+m+1; sum = 0;
memset(head, -1, sizeof(head));
getchar();
for(int i = 1; i <= m; i++)
{
gets(str);
//            printf("%s\n", str)
int len = strlen(str);
int x = 0, j = 0;
for(j = 0; j < len; j++)
{
if(str[j] == ' ') break;
x = x*10 + str[j]-'0';
}
addEdge(s, i, x);
sum += x;
x = 0;
j++;
for(; j < len; j++)
{
if(str[j] == ' ')
{
addEdge(i, x+m, INF);
x = 0;
continue;
}
if(j == len-1)
{
x = x*10 + str[j]-'0';
addEdge(i, x+m, INF);
}
x = x*10 + str[j]-'0';
}
}
for(int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
addEdge(i+m, t, x);
}
//        cout << Dinic() << endl;
//        cout << sum << endl;
int ans = sum - Dinic();
//        for(int i = head[0]; i != -1; i = edge[i].next)
//        {
//            if(edge[i].w && !(i&1)) printf("%d ", edge[i].v);
//        }
for(int i = 1; i <= m; i++)
{
if(d[i] != 0)
printf("%d ", i);
}
puts("");
//        for(int i = m+1; i <= n+m; i++)
//        {
//            for(int j = head[i]; j != -1; j = edge[j].next)
//            {
//                if(!edge[j].w && !(j&1))
//                    printf("%d ", i-m);
//            }
//        }
for(int i = 1; i <= n; i++)
{
if(d[i+m] != 0)
printf("%d ", i);
}
puts("");
printf("%d\n", ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: