抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题目传送门

题意

一个 \(H\times W\) 的矩阵,有 \(n\) 个位置有卡片,每张卡片上有个数字 \(a_i\)。你可以在每行拿走一张卡片,然后在每列拿走一张卡片,求拿走的卡片的 \(a_i\) 之和的最大值。

\(1\le H,W,n,a_i\le 10^5\)

题解

假设有 \(H+W\) 个点,第 \(1\sim H\) 个点表示行,第 \(H+1\sim H+W\) 个点表示列。第 \(i\) 行第 \(j\) 列的卡片对应一条 \(i\)\(H+j\) 的边,边权为卡片上的数字。我们把所有拿走的卡片对应的边拿出来,显然会形成一个环套树森林。简单证明一下:

假设边是有向的,在第 \(i\) 行选了第 \(j\) 列的卡片,则选择 \(i\)\(H+j\) 的有向边;在第 \(i\) 列选了第 \(j\) 行的卡片,则选择 \(H+i\)\(j\) 的有向边。那么每个点有且仅有一条出边,所以这是一个环套内向树森林。忽略边的方向,则是环套树森林。

相当于我们要求出一个原图的边权之和最大的“生成环套树森林”。用类似于求最大生成树的 Kruskal 算法贪心选边即可。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <vector>
int read(){
register int x = 0;
register char f = 1, ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f ^= 1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
return f ? x : -x;
}
int n, h, w, fa[200005], g[200005];
long long ans;
struct node{
int r, c, v;
bool operator < (const node &rhs) const {
return v > rhs.v;
}
}a[100005];
int find(int x){
return fa[x] == x ? x : (fa[x] = find(fa[x]));
}
bool merge(int x, int y){
x = find(x), y = find(y);
if (x == y) if (g[x]) return 0; else return g[x] = 1, 1;
if (g[x] && g[y]) return 0;
return fa[y] = x, g[x] |= g[y], 1;
}
int main(){
n = read(), h = read(), w = read();
for (register int i = 1; i <= n; ++i)
a[i].r = read(), a[i].c = read(), a[i].v = read();
std :: sort(a + 1, a + 1 + n);
for (register int i = 1; i <= h + w; ++i) fa[i] = i, g[i] = 0;
for (register int i = 1; i <= n; ++i)
if (merge(a[i].r, a[i].c + h)) ans += a[i].v;
printf("%lld\n", ans);
}

评论