Loading

操作分块

概念

有若干修改、询问组成的操作序列,考虑把这个序列分块,然后维护块内和块间的贡献。类似于根号分治,平衡两种维护方式之间的复杂度。

一般而言,在图上乱搞 有大量操作并且难以用普通数据结构维护的时候可以考虑操作分块。可以这样考虑:

  1. 当仅有 \(\sqrt{n}\) 次操作时,如何在 \(O(n^2)\) 内维护?

  2. 如何在单块 \(O(\sqrt{n})\) 内维护前面 \(O(n)\) 长度对于当前块的影响?

  3. 是否存在根号分治可以平衡的暴力做法?

其中复杂度根据实际情况确定,不一定是根号。

时间复杂度可能较劣。

例题

P5443 [APIO2019] 桥梁

题意

给定一张 \(n\) 个点 \(m\) 条边的无向图和 \(q\) 次询问,每条边有其边权 \(w\)。现可以:

  1. 修改某条边的边权

  2. 询问从点 \(u\) 出发,只经过边权小于等于给定值 \(k\) 的边可以到达的顶点数量

\(1 \leq n \leq 5 \times 10^4, 0 \leq m \leq 10^5, 1 \leq q \leq 10^5\)

思路

首先考虑将原操作序列分块。

有一个很显然的做法:只保留边权小于等于 \(k\) 的边,此时 \(u\) 所在的连通块大小即为答案。可以用并查集维护。

考虑块内修改对块内询问的影响:

显然当前块内没有修改过的边仍然保持前若干块的边权,于是可以直接先将这些边加入并查集。预先将所有边和块内询问按边权降序排序,则可以维护指针扫一次所有边,单块复杂度为 \(O(m \log m + m)\)

对于当前块内修改过的边,显然块长取 \(\sqrt{n}\) 时至多有 \(\sqrt{n}\) 次修改和 \(\sqrt{n}\) 次排序,于是可以考虑直接对于每个询问都扫一次块内的所有修改,之后再用可撤销并查集把块内的修改撤回,单块时间复杂度 \(O(n)\)

考虑前面若干整块对于当前块的影响:

发现直接修改对应边的边权即可,总时间复杂度 \(O(n)\)

当块长取 \(B\) 时,总时间复杂度为 \(O(B(m \log m + m + n))\)

代码

#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
using namespace std;

const int maxn = 5e4 + 5;
const int maxq = 1e5 + 5;
const int maxm = 1e5 + 5;
const int blk_sz = 320;

inline void swap(int &a, int &b) { int tmp = a; a = b, b = tmp; }

inline int read()
{
    int res = 0;
    char ch = getchar();
    while ((ch < '0') || (ch > '9')) ch = getchar();
    while ((ch >= '0') && (ch <= '9'))
    {
        res = res * 10 + ch - '0';
        ch = getchar();
    }
    return res;
}

inline void write(int x)
{
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

int n, m, q;
int st[blk_sz], ed[blk_sz];
int opt[maxq], b[maxq], r[maxq], s[maxq], w[maxq], ans[maxq];
int idx[maxm];
bool vis[maxm], chk[maxm];
vector<int> mdf, qry;

struct Edge
{
    int u, v, w, id;

    bool operator < (const Edge& rhs) const { return (w > rhs.w); }
} edge[maxm];

struct DSU
{
    int top, stk[maxn];
    int fa[maxn], sz[maxn];

    inline void init() { for (int i = 1; i <= n; i++) fa[i] = i, sz[i] = 1; }

    inline int get(int x) { return (fa[x] == x ? x : get(fa[x])); }

    inline void merge(int x, int y)
    {
        x = get(x), y = get(y);
        if (x == y) return;
        if (sz[x] > sz[y]) swap(x, y);
        stk[++top] = x;
        sz[y] += sz[x], fa[x] = y;
    }

    inline void undo(int lst)
    {
        int u;
        while (top > lst)
        {
            u = stk[top]; top--;
            sz[fa[u]] -= sz[u], fa[u] = u;
        }
    }

    inline int query(int x) { return sz[get(x)]; }
} dsu;

inline bool cmp(int a, int b) { return (w[a] > w[b]); }

int main()
{
    int lst, cur;
    int block, len;
    n = read(), m = read();
    for (int i = 1; i <= m; i++)
    {
        edge[i].id = i;
        edge[i].u = read(), edge[i].v = read(), edge[i].w = read();
    }
    q = read();
    block = sqrt(q);
    len = (q + block - 1) / block;
    for (int i = 1; i <= len; i++) st[i] = (i - 1) * block + 1, ed[i] = i * block;
    ed[len] = q;
    for (int i = 1; i <= q; i++)
    {
        opt[i] = read();
        if (opt[i] == 1) b[i] = read(), r[i] = read();
        else s[i] = read(), w[i] = read();
    }
    dsu.init();
    for (int i = 1; i <= len; i++)
    {
        lst, cur = 1;
        mdf.clear(), qry.clear();
        sort(edge + 1, edge + m + 1);
        for (int j = 1; j <= m; j++) idx[edge[j].id] = j;
        for (int j = st[i]; j <= ed[i]; j++)
        {
            if (opt[j] == 1)
            {
                vis[b[j]] = true;
                mdf.push_back(j);
            }
            else qry.push_back(j);
        }
        sort(qry.begin(), qry.end(), cmp);
        reverse(mdf.begin(), mdf.end());
        for (int j : qry)
        {
            while ((cur <= m) && (edge[cur].w >= w[j])) { if (!vis[edge[cur].id]) dsu.merge(edge[cur].u, edge[cur].v); cur++; }
            lst = dsu.top;
            for (int k : mdf)
            {
                if ((k < j) && (!chk[b[k]]))
                {
                    if (r[k] >= w[j]) dsu.merge(edge[idx[b[k]]].u, edge[idx[b[k]]].v);
                    chk[b[k]] = true;
                }
            }
            for (int k : mdf) if ((k >= j) && (!chk[b[k]]) && (edge[idx[b[k]]].w >= w[j])) dsu.merge(edge[idx[b[k]]].u, edge[idx[b[k]]].v);
            ans[j] = dsu.query(s[j]);
            for (int k : mdf) chk[b[k]] = false;
            dsu.undo(lst);
        }
        dsu.undo(0);
        for (int j = st[i]; j <= ed[i]; j++)
            if (opt[j] == 1)
            {
                vis[b[j]] = false;
                edge[idx[b[j]]].w = r[j];
            }
    }
    for (int i = 1; i <= q; i++)
        if (opt[i] == 2) write(ans[i]), putchar('\n');
    return 0;
}
posted @ 2022-08-21 16:48  kymru  阅读(257)  评论(0编辑  收藏  举报