HDU 6394 Tree 树分块或者LCT 2018杭电多校第七场

本文介绍了关于魔法树的问题,树上的每个节点都有一个权值,代表可以将魔法石传送至其祖先节点的层数。文章给出了两种类型的查询:1) 询问在某个节点投掷魔法石需要多少次传送才能移出树;2) 修改节点的权值。解决方案包括树分块和LCT算法,但主要讨论了使用树分块的方法,通过记录节点跳跃信息来高效处理查询和修改操作。

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

Tree

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 380    Accepted Submission(s): 130

Problem Description

Alice and Bob are playing with a magic tree This magic tree has n nodes,with n−1 magic paths connecting them into a connected block. Node 1 is at the top of the magic tree (layer 0). Node i is at the kth layer, where k is the distance from the node i to the node 1. Alice and Bob give a mana value on each node. If a magic stone falls on node i, it will be sent up to the k layer and appear on the kth ancestor node of the i layer(k is the mana value of node i). This node will continue to send up it, and so on. If the layer of node i is less than k, this stone will be sent out of the magic tree. Alice is curious, she will modify the magic value of a node, and ask Bob: If you drop a magic stone on the node x, how many times does it take to transfer it out of the magic tree?

Input

Input contains multiple tests
The first line contains one integer T(T≤4), indicating the number of test cases.
The following lines describe all the test cases
For each test case: The first line contains an integer n(n≤100000), indicating the size of the magic tree.
The second line has n−1 numbers, and the ith number represents the father of the node i+1.
The third row has n numbers, and the ith number represents the initial mana ai(ai≤n) value of each node.
In the fourth line, a number m(m≤100000) represents the number of operations.
The next m lines, one operation per line.
First a number op(1≤op≤2) represents the type of operation.
If op==1, a number x will be read immediately, indicating that a magic stone is thrown to the node x.
If op==2, it will immediately read in two numbers x and new_a, indicating that the magic value of node x is modified to new_a(new_a≤n).

Output

For each query with op==1, output the answer

Sample Input

1

4

1 2 3

1 1 1 1

3

1 4

2 3 2

1 4

Sample Output

4

3

 

题意:题意很明确, 就是给出一棵有根树, 然后每个点都有一个权值, 表示这个点可以传送到它的第a[i]个父亲节点上, 然后给出q个查询,每个查询有两种表示形式, 第一种:询问在x点经过几次传送可以传送出树(向上传送出根则表明传送出树) 第二种:将x点的权值更新为y

 

思路:有两种思路, 第一种是树分块, 第二种是LCT鉴于弱鸡不会LCT。。就只能说一下树分块的思路, 首先将树根据dfs序分块, 然后记录每个节点向上跳到下一个块所需要的次数, 然后再记录跳到下一个块中的那个点是多少, 对于第一种查询, 我们只需要按块暴力就行, 对于第二种修改, 我们也只需要修改与x处在同一个块里面, 且dfs序在x以下的点就可以了, 这样我们每次的复杂度都是块的大小, 或者块的个数, 这样复杂度基本可以过。 这里找父亲节点的时候要用倍增优化以下, 而且注意! 这里的块不要开根号n, 直接固定开100的大小就行了, 因为根号下1e5差不多是350, 这样的话如果每次都要修改或者查询的话是吃不消的, 不如直接开成100, 这样修改最大是100, 查询最大时1000, 这样复杂度怎么算都是差不多的。

 

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

const int maxed = 100000 + 10;

struct E
{
    int v, bef;
}e[maxed];

int n, m, ans, head[maxed], p[maxed];
int C[18], fa[maxed][18];
int block_size, block, size_[maxed], belong[maxed], cnt[maxed], id[maxed];

int main()
{
    void add_(int x, int y);
    void slove_1(int u);
    void slove_2(int u, int cur);
    for (int i = 0; i < 18; ++i)
        C[i] = (1 << i);
    int N;
    scanf("%d", &N);
    while (N--) {
        ans = 1;
        memset(head, -1, sizeof(head));
        scanf("%d", &n);
        int a;
        for (int i = 1; i <= n - 1; ++i) {
            scanf("%d", &a);
            add_(a, i + 1);
        }
        for (int i = 1; i <= n; ++i)
            scanf("%d", &p[i]);
        block_size = 100;
        memset(size_, 0, sizeof(size_));
        block = 1;
        belong[1] = 1, size_[1] = 1;
        for (int i = 0; i < 18; ++i)
            fa[1][0] = 0;
        slove_1(1);
        int b, c;
        scanf("%d", &m);
        while (m--) {
            scanf("%d%d", &a, &b);
            if (a == 1) {
                int ans = 0, now = b;
                while (true) {
                    ans += cnt[now];
                    now = id[now];
                    if (!now)
                        break;
                }
                printf("%d\n", ans);
            }
            else {
                scanf("%d", &c);
                p[b] = c;
                slove_2(b, b);
            }
        }
    }
    return 0;
}

void add_(int x, int y)
{
    e[ans].v = y;
    e[ans].bef = head[x];
    head[x] = ans++;
}

void slove_1(int u)
{
    cnt[u] = 1, id[u] = 0;
    int wa = u, w = p[u];
    while (true) {
        for (int i = 17; i >= 0; --i)
            if (C[i] <= w) {
                w -= C[i];
                wa = fa[wa][i];
                break;
            }
        if (!wa)
            break;
        if (!w) {
            if (belong[u] == belong[wa]) {
                //std::cout << u << "=====" << wa << "====" << cnt[wa] << std::endl;
                cnt[u] += cnt[wa];
                id[u] = id[wa];
            }
            else
                id[u] = wa;
            break;
        }
    }
    for (int i = head[u]; i != -1; i = e[i].bef) {
        int v = e[i].v;
        if (size_[belong[u]] < block_size) {
            size_[belong[u]] += 1;
            belong[v] = belong[u];
        }
        else {
            belong[v] = ++block;
            size_[belong[v]] += 1;
        }
        fa[v][0] = u;
        for (int j = 1; j < 18; ++j)
            fa[v][j] = fa[fa[v][j - 1]][j - 1];
        slove_1(v);
    }
}

void slove_2(int u, int cur)
{
    cnt[u] = 1, id[u] = 0;
    int wa = u, w = p[u];
    while (true) {
        for (int i = 17; i >= 0; --i)
            if (C[i] <= w) {
                w -= C[i];
                wa = fa[wa][i];
                break;
            }
        if (!wa)
            break;
        if (!w) {
            if (belong[u] == belong[wa]) {
                cnt[u] += cnt[wa];
                id[u] = id[wa];
            }
            else
                id[u] = wa;
            break;
        }
    }
    for (int i = head[u]; i != -1; i = e[i].bef) {
        int v = e[i].v;
        if (belong[v] != belong[cur])
            continue;
        slove_2(v, cur);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值