一道让我眼前一亮的题目,可以说是打cf以来我觉得最为巧妙的题目之一,强烈表扬👍。
题目大意:
m 个 set,每个set里面有一些点,点的范围是 [1,n]。
给两个序列ab。
删除第 i 个 set 的第 j 个元素,需要花费 a[i]+b[j] 的价值。
要求每两个set最多不会出现两个相同的点。
解题过程:
解题的时候出了很多假算法,最难处理的就是 个点对和贪心的策略 (因为这题可以抽象成每个set有
个数量级的点对,然后点对不会重复),光是处理点对就直接 tle 了。
在这里碰壁的人不止我一个,还有上个世纪建立电信网的专家:(历史总是如此相似...
![](https://i-blog.csdnimg.cn/blog_migrate/0deed499c0dd47c0a4735a825ef31f79.png)
果然,要用算法打败算法。用这样的方法就构建了覆盖全世界的电信网。
这题和电信网有异曲同工之处。我们可以在每个集合中创建个虚点,连接集合中的每个点,边权就是删除这个点需要的值,这样同一个集合中的两个点的连通性不就有了吗。
再来,要满足每个集合中不能出现最多两个相同的点,如果两个集合中有两个相同的点会怎么样?
会形成一个环,那不就意味这最后的图没有环,也就是一个:森林。由于要删掉的边权和最小,那么就是要保留下来的边权和最大,最大生成树,呼之欲出。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define db double
#define pii pair<int,int>
struct edge {
int from, to;
ll val;
};
int f[200010];
bool cmp(edge& x, edge& y) {
return x.val > y.val;
}
int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}
void solve() {
int n, m;
cin >> m >> n;
vector<ll> a(m + 1), b(n + 1);
for (int i = 1; i <= m; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
vector<edge> g;
ll ans = 0;
for (int i = 1; i <= m; i++) {
int x;
cin >> x;
for (int j = 1; j <= x; j++) {
int u; cin >> u;
g.push_back({ u + m,i,a[i] + b[u] });
// g.push_back({ i,u + m,a[i] + b[u] });
ans += a[i] + b[u];
}
}
sort(g.begin(), g.end(), cmp);
for (int i = 0; i <= g.size(); i++) {
f[i] = i;
}
for (auto i : g) {
int fx = find(i.from), fy = find(i.to);
if (fx != fy) {
ans -= i.val;
f[fx] = fy;
}
}
cout << ans << '\n';
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int tt = 1;
//cin >> tt;
while (tt--) {
solve();
}
}