题目描述
输入格式
输出格式
样例
数据范围与提示
题目大意:
有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=所有正权和-最小割。具体证明可以参考胡伯涛的论文。
此题要求输出方案。Dinic 算法结束(残量网络中不存在 s 到 t 的路径)时,残量网络中从 s 出发可达的所有点的集合是一个最小割的 S 集合,所以只需输出 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;
}