题意:
n个点的图,每个点有点权,每次选取一个联通分量,然后其中每个点点权-1,点权不能为负,求最少需要操作的次数
分析:
一开始用dp搞的,然后一直wa,其实这道题是并查集,看到连通分量应该想到的,还是题做少了。
因为这个题是从小到大删的点,我们可以逆向考虑,点权从大到小加入点,每加入一个点u,贡献就是(u所连接的连通分量的个数 - 1) * u的点权。
代码:
#include<bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof(a))
#define max(a,b) (a>b?a:b)
typedef long long ll;
using namespace std;
const int maxn = 1e5 + 10;
const int maxm = 2e5 + 10;
struct Edge {
int to, next;
}E[maxm << 1];
int tot, head[maxn], v[maxn];
void addEdge(int from, int to, bool istwo = false) {
E[tot] = Edge{ to,head[from] };
head[from] = tot++;
//双向边
if (istwo) {
E[tot] = Edge{ from,head[to] };
head[to] = tot++;
}
}
ll ans;
int vis[maxn], r[maxn];
int T, n, m, c;
int fa[maxn];//并查集
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void merge(int a, int b) {
a = find(a);
b = find(b);
if (a != b) fa[a] = b;
}
void solve() {
mem(vis, 0);
ans = 0;
sort(r + 1, r + 1 + n, [](int a, int b) {
return v[a] > v[b];
});
for (int i = 1; i <= n; i++) {
int now = r[i];
ans += v[now];
int Mxx = -1;
for (int j = head[now]; j != -1; j = E[j].next) {
int to = E[j].to;
if (vis[to]) {
if (find(to) != find(now)) {
merge(to, now);
ans -= v[now];
}
}
}
vis[now] = 1;
}
}
int main() {
scanf("%d", &T);
for (c = 1; c <= T; c++) {
scanf("%d%d", &n, &m);
tot = 0;
memset(head, -1, sizeof(int) * (n + 2));
for (int i = 1; i <= n; i++) {
scanf("%d", v + i);
r[i] = i; fa[i] = i;
}
for (int i = 0; i < m; i++) {
int f, t;
scanf("%d%d", &f, &t);
addEdge(f, t, true);
}
solve();
printf("%lld\n", ans);
}
}