[BZOJ2001][Hnoi2010]City 城市建设(CDQ分治+并查集)

CDQ分治。
和AHOI2013连通图差不多,但仿佛还要恶心……
基本思想是CDQ分治往下递归时,不断地缩小图的规模
下面考虑怎样处理 [l,r] [ l , r ] 范围内的操作。
(1)先找出在 [l,r] [ l , r ] 时,必须加入的边
具体地,先假设操作 [l,r] [ l , r ] 涉及到的所有边的权值都为 − ∞
然后跑一遍MST,这时候在MST上 [l,r] [ l , r ] 内的操作没有涉及到的边是必须加入的边。
利用必须加入的边将图缩点,就把图的点的规模缩小到了 O(rl) O ( r − l )
(2)找出在 [l,r] [ l , r ] 时,啃腚不会被加入的边
具体地,假设操作 [l,r] [ l , r ] 涉及到的所有边的权值都为
然后跑一遍MST,这时候不在MST上且 [l,r] [ l , r ] 内的操作没有涉及到的边是啃腚不会被加入的边。
把啃腚不会被加入的边去掉,就把图的边的规模缩小到了 O(rl) O ( r − l )

这样把图的规模缩小后,就可以往左右两边递归。
递归到叶子节点时,可以直接跑MST(这时候图的规模已经很小)。
同时:
(1)由于带有修改,所以递归完左区间要处理左区间的修改对右区间的影响。
(2)和AHOI2013连通图一样,要写一个可持久化可支持撤回上一次操作的并查集。
(3)其他细节见代码注释。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
inline int read()
{
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 2e4 + 5, M = 5e4 + 5, T = 2e6 + 5;
int n, m, q, K[M], D[M], fa[N], times, orz, pyz[T], lpf[T], tim[T], QAQ,
_u[M], _v[M], _w[M], edge[T], tmp[M], typ[M];
ll ans[M];
bool exi[M];
bool comp(int a, int b)
{
    return _w[a] < _w[b];
}
int cx(int x, bool ad)
{
    if (!ad)
    {
        while (fa[x] != x) x = fa[x];
        return x;
    }
    if (fa[x] != x)
        pyz[++orz] = x, lpf[orz] = fa[x],
        tim[orz] = times, fa[x] = cx(fa[x], ad);
    return fa[x];
}
void zm(int x, int y)
{
    times++;
    int ix = cx(x, 1), iy = cx(y, 1);
    if (ix != iy) pyz[++orz] = iy, lpf[orz] = fa[iy],
        tim[orz] = times, fa[iy] = ix;
}
void backto(int tar) //撤回
{
    while (tim[orz] > tar)
        fa[pyz[orz]] = lpf[orz], orz--;
    times = tar;
}
void orzcyxdalao(int l, int r, int nxt)
{
    //edge数组的[nxt,QAQ]区间内存下当前要处理的边
    int i, t = times, tot = 0, wr = QAQ, mid = l + r >> 1;
    if (l == r) //递归到叶子就直接跑MST,注意修改
    {
        int z = _w[K[l]];
        For (i, nxt, QAQ) tmp[++tot] = edge[i];
        _w[K[l]] = D[l];
        if (cx(_u[K[l]], 0) == cx(_v[K[l]], 0))
            ans[l] += D[l] - z;
        sort(tmp + 1, tmp + tot + 1, comp);
        For (i, 1, tot) if (cx(_u[tmp[i]], 0) != cx(_v[tmp[i]], 0))
                ans[l] += _w[tmp[i]], zm(_u[tmp[i]], _v[tmp[i]]);
        backto(t); _w[K[l]] = z;
        return;
    }
    For (i, l, r) exi[K[i]] = 1;
    For (i, nxt, QAQ) if (!exi[edge[i]]) tmp[++tot] = edge[i];
    sort(tmp + 1, tmp + tot + 1, comp);
    //第1遍:求出必须加的边
    For (i, l, r) zm(_u[K[i]], _v[K[i]]);
    For (i, 1, tot) typ[i] = 0;
    For (i, 1, tot) if (cx(_u[tmp[i]], 0) != cx(_v[tmp[i]], 0))
        typ[i] = 1, zm(_u[tmp[i]], _v[tmp[i]]);
    backto(t);
    //第2遍:求出必须不加的边
    For (i, 1, tot)
        if (cx(_u[tmp[i]], 0) != cx(_v[tmp[i]], 0))
            zm(_u[tmp[i]], _v[tmp[i]]);
        else typ[i] = 2;
    backto(t);
    ll delta = 0;
    //缩点与删边↓
    For (i, 1, tot)
        if (!typ[i]) edge[++QAQ] = tmp[i];
        else if (typ[i] == 1) zm(_u[tmp[i]], _v[tmp[i]]),
            delta += _w[tmp[i]];
    For (i, l, r) edge[++QAQ] = K[i];
    For (i, l, r) exi[K[i]] = 0;
    int sf = QAQ, ex = times;
    orzcyxdalao(l, mid, wr + 1);
    For (i, l, mid) //处理左区间对右区间的影响
    {
        if (cx(_u[K[i]], 0) == cx(_v[K[i]], 0))
            delta += D[i] - _w[K[i]];
        _w[K[i]] = D[i];
    }
    QAQ = sf; backto(ex);
    orzcyxdalao(mid + 1, r, wr + 1);
    For (i, l, r) ans[i] += delta;
    QAQ = sf; backto(ex);
}
int main()
{
    freopen("city.in", "r", stdin);
    freopen("city.out", "w", stdout);
    int i;
    n = read(); QAQ = m = read(); q = read();
    For (i, 1, n) fa[i] = i;
    For (i, 1, m)
        _u[i] = read(), _v[i] = read(), _w[i] = read();
    For (i, 1, q)
        K[i] = read(), D[i] = read();
    For (i, 1, m) edge[i] = i;
    orzcyxdalao(1, q, 1);
    For (i, 1, q) printf("%I64d\n", ans[i]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值