题解 AT5168 【Card Collector】

Lice

2020-08-04 19:35:21

Solution

### Description 现有 $h$ 行 $w$ 列的网格。给定 $n$ 张卡片,每张卡片 $i$ 有一个位置 $(r_i, c_i)$ 以及数字 $a_i$。 你可以先在每一行拿走不超过一个卡片,然后在每一列再拿不超过一个。不能重复拿。 求最后可得的卡片上数字之和的最大值。 ### Hint - $1\le n, w, h\le 10^5$ - $a_i \in [1, 10^5]$ - $1\le r_i \le h, 1\le c_i \le w$ ### Solution *Reference : https://autumnkite.github.io/atcoder-jsc19qualE-sol/* 如果把每一行、列都视作一个点,把卡片视为边,我们会得到一个 $w+h$ 个点, $n$ 条边的图。若有一张第 $i$ 行第 $j$ 列的卡片,那么就视作一条结点 $i$,$h+j$ 之间的边,权值为卡片数值。 考虑题面上取卡片的过程如何转换到图上:我们假定图有向,那么在第 $i$ 行取走第 $j$ 列的卡片,就相当于一条 $i\to j+h$ 的边;同理,在第 $i$ 列取走第 $j$ 行的卡片,就相当于一条 $i + h\to j$ 的边。 试着研究最后取完建出的新图的性质。一个结点的出度最多为 $1$,那么整个新图就是(内向)基环树的森林。最后将边转为无向。 题目要求权值最大化,那么就是求图上的最大生成基环树森林。要求最大生成基环树森林,可以仿照 Kruskal 算法贪心地取边,与一般 MST 的不同之处就是需要判环,实现要点是一个点所在连通块中最多一个环。 时间复杂度 $O(n\log n)$。 ### Code ```cpp #include <iostream> #include <algorithm> using namespace std; const int N = 1e5 + 5; int n, w, h; struct edge { int u, v, w; } e[N]; int fa[N << 1]; bool ring[N << 1]; int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } signed main() { ios::sync_with_stdio(false); cin >> n >> h >> w; for (int i = 1; i <= w + h; i++) fa[i] = i, ring[i] = false; for (int i = 1; i <= n; i++) cin >> e[i].u >> e[i].v >> e[i].w; sort(e + 1, e + 1 + n, [](const edge& x, const edge& y) { return x.w > y.w; }); long long ans = 0ll; for (int i = 1; i <= n; i++) { int u = e[i].u, v = e[i].v + h, w = e[i].w; u = find(u), v = find(v); if (u == v) { if (!ring[u]) ring[u] = 1, ans += w; } else { if (!ring[u] || !ring[v]) { ring[u] = ring[v] = ring[u] | ring[v]; fa[u] = v, ans += w; } } } printf("%lld\n", ans); return 0; } ```