P3206 [HNOI2010]城市建设 题解
相对于一般的 CDQ 分治,这题的写法略有不同。相信也难倒了不少初学者,那么我就来带大家分析一下。
如果是暴力每次跑一次 kruskal,时间复杂度肯定要爆炸。但我们可以通过分治,一层层的统一求出一定用到的边和一定用不到的边,达到减少边数的效果,最后到达边界时再进行暴力,可以起到很好的加速效果。本质上就是将单独选边转化为给一个区间的所有询问选边,起到优化时间的效果。
具体的,我们的函数 CDQ(l, r) 就是要删除对于
具体的,我们把
然后,我们再把
而计算顺序是:先按顺序执行上述计算,再计算 CDQ(l,mid),再计算 CDQ(mid+1,r)。只有这样才能保证计算 CDQ(l,r) 时,
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5, INF = 1e8;
using LL = long long;
int n, m, qq, c[N], ct[N], a[N], b[N], fa[N];
LL ans[N];
struct P {
int x, y, z, id;
bool operator < (const P &a) const {
return z < a.z;
}
} e[20][N], f[N], g[N];
int get(int x) {
return fa[x] == x ? x : fa[x] = get(fa[x]);
}
void Clear(int tot) {
for (int i = 1; i <= tot; ++i) {
fa[f[i].x] = f[i].x;
fa[f[i].y] = f[i].y;
}
}
void CDQ(int l, int r, int now, int tot, LL sum) {
if (l == r) ct[a[l]] = b[l];
for (int i = 1; i <= tot; ++i) {
e[now][i].z = ct[e[now][i].id];
c[e[now][i].id] = i;
f[i] = e[now][i];
}
if (l == r) {
ans[l] = sum;
Clear(tot);
sort(f + 1, f + tot + 1);
for (int i = 1; i <= tot; ++i) {
int x = get(f[i].x), y = get(f[i].y);
if (x == y) continue;
ans[l] += f[i].z;
fa[x] = y;
}
return;
}
Clear(tot);
for (int i = l; i <= r; ++i) f[c[a[i]]].z = -INF;
sort(f + 1, f + tot + 1);
int tt = 0;
for (int i = 1; i <= tot; ++i) {
int x = get(f[i].x), y = get(f[i].y);
if (x == y) continue;
fa[x] = y;
if (f[i].z != -INF) {
g[++tt] = f[i];
sum += f[i].z;
}
}
Clear(tot);
for (int i = 1; i <= tt; ++i) fa[get(g[i].x)] = get(g[i].y); //缩点
tt = 0;
for (int i = 1; i <= tot; ++i) {
int x = get(f[i].x), y = get(f[i].y);
if (x == y) continue;
f[++tt].x = x;
f[tt].y = y;
f[tt].z = f[i].z;
f[tt].id = f[i].id;
}
tot = tt;
for (int i = 1; i <= tot; ++i) c[f[i].id] = i;
for (int i = l; i <= r; ++i) f[c[a[i]]].z = INF;
Clear(tot);
sort(f + 1, f + tot + 1);
tt = 0;
for (int i = 1; i <= tot; ++i) {
if (f[i].z == INF) {
f[++tt] = f[i];
continue;
}
int x = get(f[i].x), y = get(f[i].y);
if (x == y) continue;
fa[x] = y;
f[++tt] = f[i];
}
tot = tt;
for (int i = 1; i <= tot; ++i) e[now + 1][i] = f[i];//方便递归到下一层时取边用。由于下一层递归不会影响到 now + 1,故没有问题。
int mid(l + r >> 1);
CDQ(l, mid, now + 1, tot, sum);
CDQ(mid + 1, r, now + 1, tot, sum);
}
int main() {
scanf("%d%d%d", &n, &m, &qq);
for (int i = 1; i <= m; ++i) {
scanf("%d%d%d", &e[0][i].x, &e[0][i].y, &e[0][i].z);
e[0][i].id = i;
ct[i] = e[0][i].z;
}
for (int i = 1; i <= qq; ++i) scanf("%d%d", a + i, b + i);
CDQ(1, qq, 0, m, 0);
for (int i = 1; i <= qq; ++i) printf("%lld\n", ans[i]);
}