P5903 树上 k 级祖先 (树上倍增)

题目链接: P5903 树上 k 级祖先

大致题意

不 要 问 我 题 目 为 什 么 带 空 格.

解题思路

树上倍增 不会吧, 不会吧, 你还没学LCA吗? (如果你学过了LCA, 那就比较好理解这个题.)

首先对于一个节点, 如果我们想求他的第k级祖先, 我们不妨把k看成二进制的形式.
假设 k = 37 = (100101)2, 那么对于节点x, 其37级祖先我们可以这样去求:

x -> x的第32级祖先(称为: x32) -> x32的第4级祖先(称为: x32,4) -> x32,4的第1级祖先(称为: x32,4,1).

最后我们得到的x32,4,1节点 即为x的第37级祖先.

我们发现实际上这个过程就是在用2的整次方凑出k. 我们需要处理的数据也很明显, 即: 对于每个节点, 其2的整次幂级祖先是谁. 我们用f[N][log2(N)] 来存储预处理出的信息.

对于一个节点x而言, 我们如果从根节点开始, 进行dfs, 那么我们一定先遍历过其父亲节点fa, 然后再到x.

即: x节点的每一个祖先节点的信息我们都已经与处理完了(因此我们可以直接利用)

当我们想算x节点的第2i级祖先时, 我们可以由 x节点的第2i-1级祖先第2i-1级祖先来得到.
即: f[x][i] = f[f[x][i - 1]][i - 1]


预处理复杂度: O(nlogn) 查询复杂度: 最坏O(logn), 与k2中1的个数相关.

AC代码

#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E5 + 10;

#define ui unsigned int
ui s;
inline ui get(ui x) {
	x ^= x << 13;
	x ^= x >> 17;
	x ^= x << 5;
	return s = x;
}

vector<int> edge[N];
int depth[N], f[N][21];

void dfs(int x, int fa) {
	depth[x] = depth[fa] + 1;
	f[x][0] = fa;
	rep(i, 20) f[x][i] = f[f[x][i - 1]][i - 1];

	for (auto& to : edge[x]) if (to != fa) dfs(to, x);
}

int getk(int x, int k) { //求k级祖先
	if (!k) return x;
	int index = log2(k); //得到最高位1的位置
    //int index = log2(k & -k); 我们也可以这样得到最低位1的位置
	return getk(f[x][index], k - (1 << index));
}

int main()
{
	int n, m; cin >> n >> m >> s;
	int root = 0;
	rep(i, n) {
		int p; scanf("%d", &p);
		if (p) edge[p].push_back(i);
		else root = i;
	}

	dfs(root, 0);

	ll ans = 0, res = 0; //ans为最终答案, res为上一次的结果, 注意开ll, 因为会超int
	rep(i, m) {
		ui x = (get(s) ^ res) % n + 1, k = (get(s) ^ res) % depth[x];
		res = getk(x, k);
		ans ^= i * res;
	}
	cout << ans << endl;
	return 0;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值