题目大意
有一棵 n 个点的有根树,标号为
0 到 n−1 , i 号点的父亲是⌊i−1k⌋ 号点,求所有子树大小的异或和。
1≤n,k≤1018 。分析
题目中 ⌊i−1k⌋ 这个的意思就是说这是一颗完全k叉树
我们容易发现以根节点的儿子为根的子树中,最多只有一颗子树是不满的,其他的都是满树。对于满树的情况我们可以很容易地求得答案。
这样就只需要递归地取处理不满地那颗字数即可。
注意k等于1的情况需要特别处理.代码
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
#include<stack>
using namespace std;
const int MAXN=10000009;
#define LL long long int
int T;
LL n,k;
LL Get_h(LL n,LL k)//n个节点的k叉树的层数
{
LL h=1;
n=n-1;
while(n!=0)
{
n=(n-1)/k;
h++;
}
return h;
}
LL f(LL n,LL k)//当满树的情况的答案
{
if(k%2==0)return n;
else
{
LL h=Get_h(n,k);
LL sum=0,x=0;
for(int i=0;i<=h;i++)
{
x=x*k+1;
sum=sum^x;
}
return sum;
}
}
LL Get_n(LL h,LL k)//高度为h的满树的节点个数
{
if(h==1)return 1;
if(h==0)return 0;
LL ans=0,x=1;
for(int i=1;i<=h;i++)
{
ans+=x;
x=x*k;
}
return ans;
}
LL Get_fa(LL n,LL k)//得到编号为n的那个节点离根节点最近的祖先的编号
{
while((n-1)/k!=0){n=(n-1)/k;}
return n;
}
LL F(LL n,LL k)//答案
{
if(n==1)return 1;
LL ans;
LL h=Get_h(n,k);
if(Get_n(h,k)==n)return f(n,k);
LL n_l=Get_n(h-1,k);
LL n_r=Get_n(h-2,k);
LL fa=Get_fa(n-1,k);
ans=F(n-1-(fa-1)*n_l-(k-fa)*n_r , k);
if( (fa-1)%2==1)ans=ans^f(n_l,k);
if( (k-fa)%2==1)ans=ans^f(n_r,k);
ans=ans^n;
return ans;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%lld%lld",&n,&k);
if(k==1)
{
if(n%4==0)cout<<n<<endl;
else if(n%4==1)cout<<1<<endl;
else if(n%4==3)cout<<0<<endl;
else cout<<n+1<<endl;
}
else cout<<F(n,k)<<endl;
}
}
/*
100
1000000000000 100000
100000000000000000 1000000000
5 2
5 3
*/