[BZOJ3351][[IOI2009]Regions][分块]

本文详细解析了BZOJ3351题目,即IOI2009 Regions问题的分块算法解决方案。通过对颜色进行分类讨论,采用树剖和前缀和技巧,最终实现高效查询特定颜色节点的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[BZOJ3351][[IOI2009]Regions][分块]

题目大意:

给定一棵N<=200000个节点的带颜色的有根树,然后给出Q<=200000个询问,对于每个询问(a,b),求所有颜色为a的子树中颜色b的数目(某一个节点出现在不同子树中要重复计算)。

思路:

这道题在某次省选模拟赛中出过,然而当时并不会做,打了树剖+前缀和,可以过N,M<=2000的部分分,现在看到这道题的原题,于是对着学长的博客学习了一发。

对于每个询问(a,b),按照颜色b在树上出现次数分类讨论, 大于N的分为一类,小于等于N的分为一类。

  • 对于后者,我们暴力对每个为b的点询问一遍,每种颜色最多出现N次,总共有q个询问,复杂度为O(qN)

  • 而对于前者,这样的颜色最多有N种,我们暴力对每个为a的点询问一遍,最多有Na, 每个a上询问Nb,复杂度为O(NN)

总复杂度为:O((N+q)N)

代码:
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> Abcd;
typedef long long ll;
const int Maxn = 200005;
namespace IO {
    inline char get(void) {
        static char buf[1000000], *p1 = buf, *p2 = buf;
        if (p1 == p2) {
            p2 = (p1 = buf) + fread(buf, 1, 1000000, stdin);
            if (p1 == p2) return EOF;
        }
        return *p1++;
    }
    inline void read(int &x) {
        x = 0; static char c;
        for (; !(c >= '0' && c <= '9'); c = get());
        for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = get());
    }
    inline void write(ll x) {
        if (!x) return (void)puts("0");
        if (x < 0) putchar('-'), x = -x;
        static short s[12], t;
        while (x) s[++t] = x % 10, x /= 10;
        while (t) putchar('0' + s[t--]);
        putchar('\n');
    }
};
int head[Maxn], sub;
struct Edge {
    int to, nxt;
    Edge(void) {}
    Edge(const int &to, const int &nxt) : to(to), nxt(nxt) {}
} edge[Maxn];
inline void add(int a, int b) {
    edge[++sub] = Edge(b, head[a]), head[a] = sub;
}
int n, R, Q, v[Maxn], Block, num[Maxn], fact[Maxn];
ll ans[Maxn], tmp[Maxn];
map<Abcd, int> cur;
vector<Abcd> s[Maxn], f[Maxn];
inline void dfs1(int u) {
    int t = v[u];
    tmp[t]++;
    for (int i = 0; i < (int)s[t].size(); i++)
        ans[s[t][i].first] += tmp[s[t][i].second];
    for (int i = head[u], v; i; i = edge[i].nxt) {
        v = edge[i].to;
        dfs1(v);
    }
    tmp[t]--;
}
inline void dfs2(int u) {
    int t = v[u];
    for (int i = 0; i < (int)f[t].size(); i++)
        ans[f[t][i].first] -= tmp[f[t][i].second];
    tmp[t]++;
    for (int i = head[u], v; i; i = edge[i].nxt) {
        v = edge[i].to;
        dfs2(v);
    }
    for (int i = 0; i < (int)f[t].size(); i++)
        ans[f[t][i].first] += tmp[f[t][i].second];
}
int main(void) {
    //freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    IO::read(n), IO::read(R), IO::read(Q);
    IO::read(v[1]);
    for (int i = 2; i <= n; i++) {
        int fa;
        IO::read(fa), IO::read(v[i]);
        add(fa, i);
    }
    Block = sqrt(n) + 1;
    for (int i = 1; i <= n; i++) num[v[i]]++;
    for (int i = 1; i <= Q; i++) {
        int a, b;
        IO::read(a), IO::read(b); fact[i] = i;
        if (cur.count(Abcd(a, b))) fact[i] = cur[Abcd(a, b)];
        else {
            cur[Abcd(a, b)] = i;
            if (num[b] > Block) f[a].push_back(Abcd(i, b));
            else s[b].push_back(Abcd(i, a));
        }
    }
    dfs1(1), dfs2(1);

    for (int i = 1; i <= Q; i++)
        IO::write(ans[fact[i]]);
    return 0;
}

完。

By g1n0st

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值