HDU 6121 Build a tree
原题连接:
http://acm.hdu.edu.cn/showproblem.php?pid=6121
题目中给出的定义。就是
k
叉堆
对于k 叉堆。
第
a
个节点的孩子编号属于区间:[ak+1,(a+1)k]
堆的高度从
0
开始算起
令F[t] 为高度为
t
的满k 叉堆对应的
answer
C[t]
为高度为
t
的满k 叉堆节点总数。
则: F[0]=1,F[t]=((k and 1)F[t−1])xor C[t] , t>1
这是因为。深度为
t
的满k 叉堆。根的每个孩子对应的答案是
F[t−1]
而它有
k
个孩子。k 个孩子相互异或。就是:
((k and 1)F[t−1])
根节点对应孩子数量为 C[t]
得: F[t]=((k and 1)F[t−1])xor C[t] , t>1
定义: answer=slove(L,R,n,k)
slove(L,R,n,k) 表示,同一层中,编号在 [L,R] 区间的节点为根的子树对答案的贡献。
HR(a)
为
a
节点对应子树的叶子节点的最小深度。
HL(a) 为
a
节点对应子树的叶子节点的最大深度。
如果HL(L)=HR(R) :
很明显。此时所有子树都是满
k
叉树(最大深度等于最小深度)
slove(L,R,n,k)=F[HL(L)] ((R−L+1) and 1)
如果 H(L)≠HR(R) :
L≠R
时:
slove(L,R,n,k)=slove(L,⌊R+L2⌋,n,k) xor slove(⌊R+L2⌋+1,R,n,k)
L=R
时 , 令
cnt[L]
为根节点为
L
的子树的节点数量:slove(L,R,n,k)=slove(l,r,n,k) xor cnt[L]
其中,[l,r]区间是L节点管辖的孩子节点所在区间。
当 k>1 时。使用上面的方法复杂度为 O(log2)
当 k=1 时。由于堆的结构的原因。会导致递归深度过深。复杂度变为 O(n)
此时问题变形为前
n
个数字的异或和
即:answer=XORni=1i
这个问题可以通过数位 dp 解决。复杂度 O(log)
下面是AC代码:
#include <math.h>
#include <stdio.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL MX=1e18;
LL F[100];
LL C[100];
LL X;
LL HR(LL a,LL n,LL k)
{
if(a+1<=n/k&&(a+1)*k<n)return HR((a+1)*k,n,k)+1;
return 0;
}
LL HL(LL a,LL n,LL k)
{
if(a<=(n-1)/k&&a*k+1<n)return HL(a*k+1,n,k)+1;
X=a;
return 0;
}
LL slove(LL L,LL R,LL n,LL k)
{
LL hr=HR(R,n,k);
LL hl=HL(L,n,k);
if(L==R)
{
if(hr==hl)return F[hr];
if(L+1<=n/k&&(L+1)*k<n)return (C[hr]+n-X)^slove(L*k+1,(L+1)*k,n,k);
return (C[hr]+n-X)^slove(L*k+1,n-1,n,k);
}
if(hr==hl)
{
if((R-L+1)&1)return F[hr];
return 0;
}
LL mid=(L+R)>>1;
return slove(L,mid,n,k)^slove(mid+1,R,n,k);
}
LL clat(LL n,LL m)
{
if(m==1)return 0;
if(n>=m)
{
if(((n-m+1)&1))return m+clat(n-m,m>>1);
return clat(n-m,m>>1);
}
return clat(n,m>>1);
}
int main ()
{
int t;
scanf("%d",&t);
while(t--)
{
LL n,k;
scanf("%lld %lld",&n,&k);
if(k==1)
{
printf("%lld\n",clat(n,1ll<<62)+(((n+1)/2)&1));
continue;
}
F[0]=1;
C[0]=1;
for(LL i=1,s=MX-1,j=1; ;j++)
{
if(s/k<i)break;
i*=k;
s-=i;
if(k&1) F[j]=F[j-1];
F[j]^=MX-s;
C[j]=C[j-1]+i;
}
printf("%lld\n",slove(0,0,n,k));
}
return 0;
}