Address
Solution
- 题目描述比较含糊……
- 首先要明确的是:购置一个仪器后可以使用多次,因此可以存在做某一个实验亏损,但和其它实验一起做反而能节省费用的情况。
- 先说下建图:
- 由源点向每个实验连边,边权为实验获得的报酬。
- 由每个实验向所需的仪器连边,边权为正无穷。
- 由每个仪器向汇点连边,边权为购置仪器的花费。
- 则 最优收益 = 所有实验的报酬总和 - 该图的最大流 。
- 容易知道:
- 若某个实验亏损,从该实验流入汇点的流量等于实验获得的报酬,减去后即相当于不做这个实验。
- 若某个实验有收益,从该实验流入汇点的流量等于仪器购置的费用,减去后即相当于计算收益。
- 本题很麻烦的一个点是输入,更麻烦的一个点是输出方案……
- 简单判断仪器到汇点满流显然是不正确的,因为若某个实验肯定亏损(即无法和其它实验一起做来节省费用),它到所需要的一些仪器的边满流,另一些仪器的边不满流,这时满流的那些仪器是不能购置的。
- 考虑每次删去一个仪器到汇点满流的边,再跑一次最大流,若原来的最大流和这时的差值等于边权,则这个仪器是必须购置的。
- 同样容易知道:
- 若某个实验肯定亏损,原来就无法满流,删去满流边后流量流入了其它的边,因此差值改变。
- 若某个实验有收益,原来就能满流,删去满流边后仍然满流,因此差值不改变。
- 最后再由仪器的购置来判断实验是否要做。
Code
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>
using namespace std;
namespace inout
{
const int S = 1 << 20;
char frd[S], *ihed = frd + S;
const char *ital = ihed;
inline char inChar()
{
if (ihed == ital)
fread(frd, 1, S, stdin), ihed = frd;
return *ihed++;
}
inline bool get(int &res)
{
char ch; res = 0; bool flag = false;
while (!isdigit(ch = inChar()) && ch != '-')
if (ch == '\n') return false;
(ch == '-' ? flag = true : res = ch ^ 48);
while (isdigit(ch = inChar()))
res = res * 10 + ch - 48;
return ch == '\n' ? false : true;
}
};
using namespace inout;
const int Maxn = 0x3f3f3f3f;
const int N = 205, M = 10005;
int lst[N], nxt[M], to[M], cst[M], cur[N], que[N], lev[N];
int n, m, src, des, qr, sum, Ans, T = 1, a[N], b[N][N];
bool cut[M], stp[N];
inline void Link(int x, int y, int z)
{
nxt[++T] = lst[x]; lst[x] = T; to[T] = y; cst[T] = z;
nxt[++T] = lst[y]; lst[y] = T; to[T] = x; cst[T] = 0;
}
inline bool Bfs()
{
for (int i = 1; i <= des; ++i) cur[i] = lst[i], lev[i] = -1;
que[qr = 1] = src; lev[src] = 0; int x, y;
for (int j = 1; j <= qr; ++j)
for (int i = lst[x = que[j]]; i; i = nxt[i])
if (!cut[i] && cst[i] > 0 && lev[y = to[i]] == -1)
{
lev[y] = lev[x] + 1; que[++qr] = y;
if (y == des) return true;
}
return false;
}
inline int Min(int x, int y) {return x < y ? x : y;}
inline int Dinic(int x, int flow)
{
if (x == des) return flow;
int y, res = 0, Del;
for (int &i = cur[x]; i; i = nxt[i])
if (!cut[i] && cst[i] > 0 && lev[y = to[i]] > lev[x])
{
Del = Dinic(y, Min(flow - res, cst[i]));
if (Del)
{
cst[i] -= Del; cst[i ^ 1] += Del;
res += Del; if (res == flow) break;
}
}
if (res != flow) lev[x] = -1;
return res;
}
int main()
{
get(m); get(n); int x;
src = n + m + 1; des = src + 1;
for (int i = 1; i <= m; ++i)
{
get(x); Link(src, i, x); Ans += x;
while (get(x)) Link(i, (b[i][++b[i][0]] = x) + m, Maxn);
if (x) Link(i, (b[i][++b[i][0]] = x) + m, Maxn);
}
for (int i = 1; i <= n; ++i)
get(x), Link(i + m, des, a[i + m] = x);
while (Bfs()) sum += Dinic(src, Maxn);
for (int i = lst[des]; i; i = nxt[i])
if (cst[i] == a[to[i]])
{
cut[i] = cut[i ^ 1] = true;
int res = 0, w = cst[i];
for (int j = 1; j <= des; ++j)
for (int k = lst[j]; k; k = nxt[k])
if (!cut[k] && (j < to[k] || j == src))
cst[k] += cst[k ^ 1], cst[k ^ 1] = 0;
while (Bfs()) res += Dinic(src, Maxn);
if (sum - res == cst[i]) stp[to[i] - m] = true;
cut[i] = cut[i ^ 1] = false;
}
for (int i = 1; i <= m; ++i)
{
bool flag = false;
for (int j = 1; j <= b[i][0]; ++j)
if (!stp[b[i][j]])
{
flag = true;
break;
}
if (!flag) printf("%d ", i);
}
putchar('\n');
for (int i = 1; i <= n; ++i)
if (stp[i]) printf("%d ", i);
printf("\n%d\n", Ans - sum);
return 0;
}