题意:
有一棵n个点的k叉树,标号为0到n-1。求所有子树大小的异或和。
思路:
首先从顶向下进行预处理,得出每一种高度的完全k叉树的各个参量,对于此题我是用三个数组保存:
c
n
t
[
i
]
:
cnt[i]:
cnt[i]: 完全
k
k
k叉树第
i
i
i层的节点个数
s
u
m
[
i
]
:
sum[i]:
sum[i]: 有
i
i
i层的完全
k
k
k叉树的所有节点个数
X
o
r
[
i
]
:
Xor[i]:
Xor[i]: 有
i
i
i层的完全
k
k
k叉树所有子树的异或和
然后我们可以考虑从当前大规模未知情况向小规模已知情况进行DFS。
考虑任意一个由
n
n
n个节点构成的高度为
H
H
H的
k
k
k叉树
对于根节点的
k
k
k个儿子构成的
k
k
k个子树,
一定有一部分构成了一个高度为
H
−
1
H-1
H−1的完全
k
k
k叉树,假设数量为
n
u
m
1
num_1
num1
另外有一部分构成了一个高度为
H
−
2
H-2
H−2的完全
k
k
k叉数,假设数量为
n
u
m
2
num_2
num2
对于这两部分,我们的答案是能够
O
(
1
)
O(1)
O(1)算出的,即:
A
n
s
=
F
(
X
o
r
[
H
−
1
]
,
n
u
m
1
)
(
x
o
r
)
F
(
X
o
r
[
H
−
2
]
,
n
u
m
2
)
Ans = F(Xor[H-1],num_1) \ (xor) \ F(Xor[H-2],num_2)
Ans=F(Xor[H−1],num1) (xor) F(Xor[H−2],num2)
其中 F ( x , y ) F(x,y) F(x,y) 表示将 x x x连续异或 y y y次。
而此时一定只有一个儿子的子树构成了一个高度为
H
−
1
H-1
H−1的非完全k叉树。
对于这一部分的答案,细心的话可以发现刚好是当前问题的一个小规模问题,故直接至下而上的搜索即可。
另外对于k=1应该特判,打表找规律即可。
代码:
#include<cstdio>
using namespace std;
typedef long long ll;
const int A = 60 + 5;
ll cnt[A],sum[A],Xor[A],n,k;
ll F(ll a,ll b){ //返回b个a异或的值
return b&1?a:0;
}
ll dfs(ll dep,ll add){ //dep:完全k叉树的层数 add:dep层完全k叉树下面的多余节点
if(dep == 0) return 0;
return (sum[dep] + add) ^ F(sum[dep],add/cnt[dep]) ^ F(sum[dep-1],k - 1 - add/cnt[dep]) ^ dfs(dep-1,add%cnt[dep]);
}
ll solve(){
if(k == 1){ // k == 1特判
if(n%4 == 0) return n;
if(n%4 == 1) return 1;
if(n%4 == 2) return n+1;return 0;
}
int dep = 1;
cnt[dep] = sum[dep] = Xor[dep] = 1;
while((n-sum[dep])/k >= cnt[dep]){ //if(所有节点 - 已经用来构成dep层完全k叉树的节点)即剩下的节点还可以继续构成dep+1层完全k叉树
dep++;
cnt[dep] = cnt[dep-1]*k;
sum[dep] = sum[dep-1] + cnt[dep];
Xor[dep] = sum[dep] ^ F(Xor[dep-1],k);
}
return dfs(dep,n-sum[dep]);
}
int main(){
int T;scanf("%d",&T);
while(T--){
scanf("%I64d%I64d",&n,&k);
printf("%I64d\n",solve());
}
return 0;
}