Build a treeTime Limit: 2000/1000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)Total Submission(s): 534 Accepted Submission(s): 167
Problem Description
HazelFan wants to build a rooted tree. The tree has
n
nodes labeled
0
to
n−1
, and the father of the node labeled
i
is the node labeled
⌊i−1k⌋
. HazelFan wonders the size of every subtree, and you just need to tell him the XOR value of these answers.
Input
The first line contains a positive integer
T(1≤T≤5)
, denoting the number of test cases.
For each test case: A single line contains two positive integers n,k(1≤n,k≤1018) .
Output
For each test case:
A single line contains a nonnegative integer, denoting the answer.
Sample Input
Sample Output
Source
|
题意 : 给出 n 和 k,表示有n个节点的 k 叉树,要求算出所有子树的节点个数的异或值
n 和 k 给的是 10^18 很显然是不能去直接计算的,但是我们直到 异或的特性 a ^ a = 0 , a ^ 0 = a
而题目给出的树是一棵完全k叉树(labeled i is the node labeled ⌊i−1k⌋ .),也就是说对于同一层非叶子节点来说
他们顶上的节点数都是一样的,这个特性要利用好。
思路 :
如果 k = 1,那么这棵树成链状,结果就是 1^2^3^...^n ,会发现它们每4次出现一个0,所以模4之后可以直接判断结果
k != 1 的时候,我们先计算出这棵树的高度,然后从上到下(根节点开始)打出每一层上面的节点个数(用tree[])
打完这个表后,如果要求叶子节点个数 可以 n - tree[depth - 1] , 先处理叶子节点,然后从倒数第二层向上计算就可以了,利用异或的特性,判断奇偶就可以节省大量的计算时间,然后注意处理不满的子树即可
#include<cstdio>
using namespace std;
#define ll long long
#define maxn 1000000
ll tree[200];
ll Pow(ll a,int b){ //快速幂
ll ans = 1;
ll tmp = a;
while(b){
if(b & 1){
ans *= tmp;
}
tmp *= tmp;
b>>=1;
}
return ans;
}
void build_tree(int depth,ll k){
for(int i = 0;i <= depth;i++){
tree[i] = (Pow(k,i) - 1) / (k - 1);
} // tree[i] 存第i层 上方的节点个数
}
int main(){
int t;
ll n,k,ans,lid,rid,lsum,rsum;
scanf("%d",&t);
while(t--){
scanf("%lld %lld",&n,&k);
if(k == 1){ //当 k = 1的时候,成链 就是 1^2^3^...^n 其中是每 4 个一循环到 0
int tmp = n & 3;
if(tmp == 0)
ans = n;
else if(tmp == 1)
ans = 1;
else if(tmp == 2)
ans = n + 1;
else
ans = 0;
printf("%lld\n",ans);
continue;
}
int depth = 1; //建立树的根节点层数为 1
for(ll m = n - 1;m > 0;depth++){
m = (m - 1) / k;
} //计算出这棵树的最大层数
build_tree(depth + 2,k);
ans = n; //根节点的值为 n
ans ^= (n - tree[depth - 1]) & 1; //先把叶子节点的处理了,都为 1 ,有奇数个则异或 1
depth--;
ll p = (n - 2) / k; // p 为 最后一个叶子的父节点
for(int id = 2;p > 0;p = (p - 1) / k,id++,depth--){ //从倒数第二层开始
lid = tree[depth - 1]; // lid 记录 该层最左边的下标
rid = tree[depth] - 1; // rid 记录 该层最右边的下标
lsum = tree[id]; // 由于我们记的是从上到下的个数,所以倒数第二层的满子树节点数
// 等于从上往下加到第二层的 节点数
rsum = tree[id - 1]; // 在右边的子树是不满的,所以要少一层
if((p - lid) & 1){ // p - lid 奇数个则表示有偶数个子树,对其中一棵树一定是满的
ans ^= lsum; // 另一棵不一定是满的会在最后处理
}
if((rid - p) & 1){ // 右边的都是不满的,且节点数可以确定 rid - p 就是棵数
ans ^= rsum; // 奇数棵直接异或,偶数棵抵消
}
ll op = p;
while(op <= (n - 2) / k){ //找到当前可能不满的子树的最左下角叶子
op = op * k + 1;
}
ans ^= (tree[id - 1] + n - op); //算出这棵可能不满树 的真正节点数
}
printf("%lld\n",ans);
}
return 0;
}