简单数据结构题(from 钟子谦——IOI2018集训队自选题)

本文介绍了一道关于树形结构的操作题,通过巧妙利用Trie树实现节点周围一圈点权的更新与查询,重点在于如何高效进行集体加1与异或和的计算。

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

简单数据结构题(from 钟子谦——IOI2018集训队自选题)

试题描述

给一棵 \(n\) 个点的树,点权开始为 \(0\) ,有 \(q\) 次操作,每次操作是选择一个点,把周围一圈点点权 \(+1\)(一个点周围的点为与该点距离为 \(1\) 的点),在该操作后你需要输出当前周围一圈点点权的异或和。

由于输出量较大,设第 \(i\) 个询问输出为 \(ans_i\),你只需要输出

\begin{equation}
[\sum^q_{i=1}ans_i \cdot (i^2+i)] \texttt{ mod } (10^9+7)
\notag
\end{equation}

输入

第一行两个数 \(n\)\(q\) ,表示树的点数和操作数。

接下来 \(n-1\) 行每行两个数表示树上的一条边。

接下来 \(q\) 行每行一个数 \(x\),表示把 \(x\) 周围一圈点点权 \(+1\)

输出

输出一个 \([0,10^9+7)\) 的数,详见题目描述。

输入示例
5 10
1 2
2 3
2 4
3 5
1
5
2
4
3
5
4
2
3
1
输出示例
2060
数据规模及约定

对于 \(80\texttt{%}\) 的数据,保证 \(n = 1000\)

对于 \(90\texttt{%}\) 的数据,保证 \(n = 100000\)

对于 \(100\texttt{%}\) 的数据,保证 \(n = 500000\)

题解

这题差最后一步想到了。

不过好像除了 trie 树这步最妙这题也没其他什么了。。。

每个点周围的点可以分为父节点和子树中的一层节点,所以我们可以把一个节点 \(u\) 的所有儿子的信息存储到节点 \(u\) 中,然后对于一个询问,父亲单独计算,统一计算所有儿子的。

所以现在要一个可以支持删除、插入(对父亲的单独操作)、全体加 \(1\)、求全体异或和的数据结构。

一个数 \(x\) 加上 \(1\) 可以看做 \(x \rightarrow x \bigoplus (2 \cdot lowbit(\texttt{~}x) - 1)\)\(\bigoplus\) 表示异或运算符,\(\texttt{~}\) 表示取反运算符,\(lowbit(t)\) 表示只取 \(t\) 最低位的 \(1\)),所以我们需要维护集合内所有 \(x\) 的同时维护所有的 \(lowbit(\texttt{~}x)\),确切地,只需要知道每种 \(lowbit(x)\)(最多 \(\log q\) 种)的取值有多少个。

然后就是我没想到的最后一步了:建立 trie 树保存集合内所有数(从浅到深按从低位到高位的顺序保存),\(lowbit(\texttt{~}x)\) 很好得到了,然后全体 \(+1\) 就是每次交换左右子树,然后递归到左子树去(即最低位异或 \(1\) 然后处理进位)。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
    return x * f;
}

#define maxn 500010
#define maxm 1000010
#define maxnode 10000010
#define maxlog 25
#define MOD 1000000007
#define LL long long

int n, m, head[maxn], nxt[maxm], to[maxm], Fa[maxn], at[maxn];
int ToT, rt[maxn], val[maxnode], ch[maxnode][2], siz[maxnode], fa[maxnode], rec[maxlog], cntr;

void AddEdge(int a, int b) {
    to[++m] = b; nxt[m] = head[a]; head[a] = m;
    swap(a, b);
    to[++m] = b; nxt[m] = head[a]; head[a] = m;
    return ;
}

int getnode() {
    int u;
    if(cntr) u = rec[cntr--];
    else u = ++ToT;
    ch[u][0] = ch[u][1] = fa[u] = siz[u] = 0;
    return u;
}
int Insert(int& r, int v) {
    if(!r) r = getnode(), val[r] = v;
    else val[r] ^= v;
    int u = r; siz[u]++;
    rep(i, 1, 20) {
        int x = v & 1; v >>= 1;
        if(!ch[u][x]) fa[ch[u][x] = getnode()] = u;
        u = ch[u][x]; siz[u]++;
    }
//  printf("Insert(%d): %d\n", r, u);
    return u;
}

void build(int u) {
    rt[u] = ++ToT;
    for(int e = head[u]; e; e = nxt[e]) if(to[e] != Fa[u]) {
        at[to[e]] = Insert(rt[u], 0);
        Fa[to[e]] = u;
        build(to[e]);
    }
    return ;
}

int getnum(int u) {
    int res = 0; cntr = 0;
    while(u) {
        if(fa[u] && ch[fa[u]][1] == u) res = res << 1 | 1;
        else if(fa[u]) res <<= 1;
//      printf("getnum: %d\n", u);
        siz[u]--;
        if(!siz[u] && fa[u]) {
            rec[++cntr] = u;
//          printf("recycle %d\n", u);
            if(ch[fa[u]][1] == u) ch[fa[u]][1] = 0;
            else ch[fa[u]][0] = 0;
        }
        if(!fa[u]) val[u] ^= res;
        u = fa[u];
    }
    return res;
}
void Add(int r) {
    int u = r, d = 0;
    while(u) {
        int s = ch[u][0] ? siz[ch[u][0]] : 0;
        if(!ch[u][1]) s = siz[u];
        if(s & 1) val[r] ^= (1 << d + 1) - 1;
        u = ch[u][1]; d++;
    }
    u = r;
    while(u) {
        swap(ch[u][0], ch[u][1]);
        u = ch[u][0];
    }
    return ;
}

int main() {
    n = read(); int q = read();
    rep(i, 1, n - 1) {
        int a = read(), b = read();
        AddEdge(a, b);
    }
    
    Insert(rt[0], 0);
    build(1);
    int Ans = 0;
    rep(i, 1, q) {
        int u = read(), ans = 0;
        if(Fa[u]) {
            ans = getnum(at[Fa[u]]) + 1;
//          printf("faans: %d\n", ans);
            at[Fa[u]] = Insert(rt[Fa[Fa[u]]], ans);
        }
//      printf("beforeAdd: %d\n", val[rt[u]]);
        Add(rt[u]);
        ans ^= val[rt[u]];
//      printf("afterAdd: %d\n", val[rt[u]]);
        (Ans += (LL)ans * ((LL)i * i % MOD + i) % MOD) %= MOD;
    }
    printf("%d\n", Ans);
    
    return 0;
}

转载于:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/7834859.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值