原题: P1361 小M的作物
题面:
P1361 小M的作物
题目描述
小 M 在 MC 里开辟了两块巨大的耕地 和 (你可以认为容量是无穷),现在,小 P 有 种作物的种子,每种作物的种子有 个(就是可以种一棵作物),编号为 到 。
现在,第 种作物种植在 中种植可以获得 的收益,在 中种植可以获得 的收益,而且,现在还有这么一种神奇的现象,就是某些作物共同种在一块耕地中可以获得额外的收益,小 M 找到了规则中共有 种作物组合,第 个组合中的作物共同种在 中可以获得 的额外收益,共同种在 中可以获得 的额外收益。
小 M 很快的算出了种植的最大收益,但是他想要考考你,你能回答他这个问题么?
输入格式
第一行一个整数 ,表示作物种数。
第二行 个整数,表示 。
第三行 个整数,表示 。
第四行一个整数 ,表示组合种数。
接下来 行中,第 行第一个整数 ,表示第 个作物组合中的作物种数,接下来两个整数 ,然后 个整数,表示该组合中的作物编号。
输出格式
只有一行,包括一个整数,表示最大收益。
输入输出样例 #1
输入 #1
3
4 2 1
2 3 2
1
2 3 2 1 2
输出 #1
11
说明/提示
样例解释
耕地种作物 , 耕地种作物 ,收益: 。
数据范围
对于 的数据,,。题目当中出现的所有权值均为不大于 的非负整数。
同样的,为最小割的练习。
观察题目,可以得到每种作物只能种在 其中一块田地中,因此最终的状态分成两个部分,每个部分相互独立,然后分别由其中的每种作物提供贡献。
我们可以先考虑较为简单的情况,即不考虑分组对贡献的影响,对于任意一种作物 ,其只能提供 或者 的贡献。因此我们可以考虑将这些贡献抽象成一种与田地 之间的联系,当你与 产生联系的时候,你就不能与 产生联系,反之亦然。这个过程可以看成点 与点 之间各有一条边,边权为 ,当我们想要保留边 时,就应该断掉边 ,像这样对每种作物都执行相同的连边操作,最终我们要得到最多的收获,即相当于那些被断掉的边的边权之和最小,这就等价于求最小割,其中 分别位于两个集合。
现在我们再来考虑对于分组的情况,我们应该怎么去处理。对于每一个分组,我们将这一个组看作一个整体,这个整体就可以像普通的作物一样,分别对点 连边,其中边权分别为 ,但是又有不同的是,我们需要考虑这个分组中每种作物与 间的连通性来判断是否要断掉与 之间的连边。
即假如说有 ,且存在边 ,则此时我们需要断掉边 ,反之亦然。所以我们需要形成这样一个模式:当我们选择保留一个组 代表的虚拟节点与 之间的连边时,则这个组内所有的作物都需要保留。在实际操作中,我们可以使得作物与组代表的虚拟节点间的连边的边权为一个极大值 ,使得最小割的判定只取决于作物和虚拟节点分别与 之间的连通性。
#include <iostream>
#include <cstring>
#include <iomanip>
#include <cmath>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(x) cout << #x << "=" << x << "\n";
int n, m;
const int maxn = 50010, maxm = 2000010;
const int INF = (1 << 31) - 1;
struct Edge
{
int to, cap, next;
} edge[maxm];
int cur[maxn], head[maxn], level[maxn];
int tot = 2;
int a[maxn], b[maxn];
void add_edge(int u, int v, int c)
{
edge[tot] = {v, c, head[u]};
head[u] = tot++;
edge[tot] = {u, 0, head[v]};
head[v] = tot++;
}
bool bfs(int s, int t)
{
queue<int> q;
q.push(s);
memset(level, -1, sizeof(level));
level[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
if (u == t)
return true;
for (int i = head[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if (level[v] == -1 && edge[i].cap > 0)
{
level[v] = level[u] + 1;
q.push(v);
}
}
}
return false;
}
int dfs(int u, int t, int flow)
{
if (u == t)
return flow;
int used = 0;
for (int &i = cur[u]; i; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].cap <= 0)
continue;
if (level[v] == level[u] + 1)
{
int w = dfs(v, t, min(flow - used, edge[i].cap));
if (w)
{
edge[i].cap -= w;
edge[i ^ 1].cap += w;
used += w;
if (used == flow)
break;
}
}
}
return used;
}
int solve(int s, int t)
{
int max_flow = 0;
while (bfs(s, t))
{
memcpy(cur, head, sizeof(head));
max_flow += dfs(s, t, INF - 1);
}
return max_flow;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int total_weight = 0;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
cin >> b[i];
cin >> m;
int s = 0, t = n + 2 * m + 1;
for (int i = 1; i <= n; i++)
{
total_weight += a[i] + b[i];
add_edge(s, i, a[i]);
add_edge(i, t, b[i]);
}
for (int i = 1; i <= m; i++)
{
int k, c1, c2;
cin >> k >> c1 >> c2;
total_weight += c1 + c2;
int combine_node_a = n + i;
int combine_node_b = n + m + i;
add_edge(s, combine_node_a, c1);
add_edge(combine_node_b, t, c2);
int p;
for (int j = 1; j <= k; j++)
{
cin >> p;
add_edge(combine_node_a, p, INF);
add_edge(p, combine_node_b, INF);
}
}
int ans = total_weight - solve(s, t);
cout << ans;
return 0;
}
与这道题类似的还有:
它们都包含了一个相似的问题:分成了两个大的集合,对于每一个小的元素,只能归属于其中一个集合。像这样的问题,我们可以使用最小割来求解。