BZOJ 2001 City城市建设 (CDQ分治 + 并查集)

/**
思路: 参照 http://blog.sina.com.cn/s/blog_6e63f59e0101blum.html
主要想法就是solve(l, r)的时候, 把区间[l, r]修改的边先置为正无穷,找出一定不要的边,之后恢复状态置为负无穷,找出一定要的边,然后递归解决
solve(l, mid), solve(mid + 1, r)
*/
#include
typedef long long ll;
const int maxn = 2e4 + 10;
const int maxm = 5e4 + 10;
const int INF = 1e9 + 10;
using namespace std;

struct edge {
    int x, y, w, pos;
    bool operator < (edge e) const { return w < e.w; }
} e[20][maxm], d[maxm], t[maxm];
ll ans[maxm];
int n, m, q, x, y;
int cost[maxm], f[maxn], mp[maxm];
int id[maxm], co[maxm];

int findset(int x) { return x == f[x] ? x : f[x] = findset(f[x]); }
void init(int tot) { for(int i = 1; i <= tot; i++) f[d[i].x] = d[i].x, f[d[i].y] = d[i].y; }

void delete_edge(int &tot) {
    int tmp = 0; init(tot); sort(d + 1, d + tot + 1);
    for(int i = 1; i <= tot; i++) {
        int nx = findset(d[i].x), ny = findset(d[i].y);
        if(nx != ny) {  ///待选择边
            f[nx] = ny; t[++tmp] = d[i]; mp[d[i].pos] = tmp;
        } else if(d[i].w == INF) {
            t[++tmp] = d[i]; mp[d[i].pos] = tmp;
        }
    }
    for(int i = 1; i <= tmp; i++) d[i] = t[i]; tot = tmp;
}

void add_edge(int &tot, ll &cnt) {
    int tmp = 0; init(tot); sort(d + 1, d + tot + 1);
    for(int i = 1; i <= tot; i++) {
        int nx = findset(d[i].x), ny = findset(d[i].y);
        if(nx != ny) { f[nx] = ny; t[++tmp] = d[i]; }
    }
    for(int i = 1; i <= tmp; i++) { f[t[i].x] = t[i].x; f[t[i].y] = t[i].y; }
    for(int i = 1; i <= tmp; i++) {
        int nx = findset(t[i].x), ny = findset(t[i].y);
        if(nx != ny && t[i].w != -INF) { f[nx] = ny; cnt += t[i].w; } ///必选边
    }
    tmp = 0;
    for(int i = 1; i <= tot; i++) {
        int nx = findset(d[i].x), ny = findset(d[i].y);
        if(nx == ny) continue;
        t[++tmp] = d[i]; t[tmp].x = nx; t[tmp].y = ny; mp[d[i].pos] = tmp; ///缩点,加入待选择边,映射关系改变
    }
    for(int i = 1; i <= tmp; i++) d[i] = t[i]; tot = tmp;
}

void solve(int l, int r, int tot, ll cnt, int lv) {
    if(l == r) cost[id[l]] = co[l];
    for(int i = 1; i <= tot; i++) e[lv][i].w = cost[e[lv][i].pos];
    for(int i = 1; i <= tot; i++) d[i] = e[lv][i], mp[d[i].pos] = i; ///共有tot条待选择边,修改实际第pos条边就是修改d中第i条边
    if(l == r) {
        ans[l] = cnt; init(tot); sort(d + 1, d + tot + 1);
        for(int i = 1; i <= tot; i++) {
            int nx = findset(d[i].x), ny = findset(d[i].y);
            if(nx != ny) { f[nx] = ny; ans[l] += d[i].w; }
        }
        return ;
    }
    ///因为区间[L, R]是待处理区间(未确定的边), 故修改的情况一定有mp关系对应, 不用去初始化mp数组
    for(int i = l; i <= r; i++) d[mp[id[i]]].w = -INF; add_edge(tot, cnt);  ///处理必选边
    for(int i = l; i <= r; i++) d[mp[id[i]]].w = INF;  delete_edge(tot); ///处理必删边
    for(int i = 1; i <= tot; i++) e[lv + 1][i] = d[i];  ///下一层分治的待选择边
    int mid = (l + r) >> 1;
    solve(l, mid, tot, cnt, lv + 1);
    solve(mid + 1, r, tot, cnt, lv + 1);
}

int main() {
    while(scanf("%d %d %d", &n, &m, &q) != EOF) {
        for(int i = 1; i <= m; i++) {
            scanf("%d %d %d", &x, &y, &cost[i]);
            e[0][i].x = x; e[0][i].y = y; e[0][i].w = cost[i]; e[0][i].pos = i;
        }
        for(int i = 1; i <= q; i++) scanf("%d %d", &id[i], &co[i]);
        solve(1, q, m, 0, 0);
        for(int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
    }
    return 0;
}

你可能感兴趣的:(图论)