COGS-2282 [HZOI 2015]黑树白(树状数组+树链剖分)

2282. [HZOI 2015]黑树白

★★   输入文件: D_Tree.in   输出文件: D_Tree.out    简单对比
时间限制:3.5 s   内存限制:512 MB

【题目描述】

给定一棵树,每个节点有点权,要求维护以下两个操作:

1、Q u v a b 本蒟蒻施展大魔法,使得树上所有点权>=a且点权<=b的点变成白点,其余的点变成黑点,并查询u到v的简单路径上有多少个白点

2、M u v 本蒟蒻施展小魔法,将u点的点权改为v

【输入格式】

第一行n,m 表示节点总数和操作次数

以下n个正整数wi表示i点的点权

以下n-1行,每行u,v描述一条边的端点

以下m行,每行一个操作如题意

注意:由于本蒟蒻的魔法太弱不稳定

所以对于每次Q和M输入的u和v,你需要令u=(u+ans)%n+1,v=(v+ans)%n+1

其中ans为上一次的答案,最开始ans=0

注意只是Q和M中输入的u和v

n,m<=80000,任意时刻点权不会超过n

【输出格式】

对于每个Q操作,输出相应答案

【样例输入】

5 5
4 3 3 1 1
2 1
3 2
4 3
5 4
M 2 1
M 4 4
M 1 4
Q 2 4 2 3
Q 4 2 1 5

【样例输出】

1
4
题解:树状数组+树链剖分

对于每个节点用dfs序编号,查询就变成dfs序为[l,r]之间,权值为[a,b]之间的点的个数,转换成二维平面问题就是求矩形内点的个数,不过因为是强制在线,因此不能用扫描线求解。

由于权值不会超过n,因此可以对于每种权值都建一棵线段树,然后用树状数组维护
树状数组保存权值不超过x时指向的线段树的根节点编号,线段树保存dfs序为[l,r]之间的点的个数
组合起来就是权值不超过x且dfs序在[l,r]之间的点的个数

#include<bits/stdc++.h>
using namespace std;
const int MX = 2e5 + 5;
const int MXM = 3e7 + 5;
int head[MX], a[MX];
int dfn[MX], sz[MX], son[MX], pre[MX], tp[MX], dep[MX];
int n, m, tot, rear;
int sum[MXM], ls[MXM], rs[MXM], rt[MXM], cnt;
void update(int p, int v, int l, int r, int prt,int& rt) {
    rt = ++cnt;
    sum[rt] = sum[prt];
    ls[rt] = ls[prt]; rs[rt] = rs[prt];
    sum[rt] += v;
    if (l == r) return;
    int m = (l + r) >> 1;
    if (p <= m) update(p, v, l, m, ls[prt],ls[rt]);
    else update(p, v, m + 1, r, rs[prt],rs[rt]);
}
int query(int L, int R, int l, int r, int rt) {
    if (l >= L && r <= R) return sum[rt];
    int m = (l + r) >> 1, ret = 0;
    if (L <= m) ret += query(L, R, l, m, ls[rt]);
    if (R > m) ret += query(L, R, m + 1, r, rs[rt]);
    return ret;
}
struct Tree {
    int n;
    vector <int> T;
    void init (int sz) {
        T.clear();
        n = sz;
        T.resize(n + 1);
    }
    void add (int x, int p, int d) {
        for (int i = x; i <= n; i += i & -i)
            update(p, d, 1, n, T[i], T[i]);
    }
    int sum (int x, int l, int r) {
        if (x > n) x = n;
        int ret = 0;
        for (int i = x; i > 0; i -= i & -i)
            ret += query(l, r, 1, n, T[i]);
        return ret;
    }
    int sum(int a, int b, int l, int r) {
        return sum(b, l, r) - sum(a - 1, l, r);
    }
} T;

struct Edge {
    int v, nxt;
} E[MX * 2];
void init() {
    memset(head, -1, sizeof(head));
    tot = rear = cnt = 0;
}
void add_edge(int u, int v) {
    E[tot].v = v;
    E[tot].nxt = head[u];
    head[u] = tot++;
}
void dfs(int u, int fa) {
    pre[u] = fa;
    sz[u] = 1; son[u] = 0;
    for (int i = head[u]; ~i; i = E[i].nxt) {
        int v = E[i].v;
        if (v == fa) continue;
        dfs(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}
void DFS(int u, int top) {
    dfn[u] = ++rear; tp[u] = top;
    dep[u] = dep[pre[u]] + 1;
    if (son[u]) DFS(son[u], top);
    for (int i = head[u]; ~i; i = E[i].nxt) {
        int v = E[i].v;
        if (v == pre[u] || v == son[u]) continue;
        DFS(v, v);
    }
}
int main() {
    //freopen("in.txt", "r", stdin);
    freopen("D_Tree.in", "r", stdin);
    freopen("D_Tree.out", "w+", stdout);
    scanf("%d%d", &n, &m);
    init();
    for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
    for (int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        add_edge(u, v); add_edge(v, u);
    }
    dfs(1, 0); DFS(1, 1);
    T.init(n);
    for (int i = 1; i <= n; i++) T.add(a[i], dfn[i], 1);
    char op[2];
    for (int i = 1, u, v, l, r, ans = 0; i <= m; i++) {
        scanf("%s%d%d", op, &u, &v);
        u = (u + ans) % n + 1; v = (v + ans) % n + 1;
        if (op[0] == 'Q') {
            scanf("%d%d", &l, &r);
            ans = 0;
            for (int f1 = tp[u], f2 = tp[v]; f1 != f2;) {
                if (dep[f1] < dep[f2]) swap(f1, f2), swap(u, v);
                ans += T.sum(l, r, dfn[f1], dfn[u]);
                u = pre[f1]; f1 = tp[u];
            }
            if (dep[u] > dep[v]) swap(u, v);
            ans += T.sum(l, r, dfn[u], dfn[v]);
            printf("%d\n", ans);
        } else {
            T.add(a[u], dfn[u], -1);
            T.add(a[u] = v, dfn[u], 1);
        }
    }
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值