\(tree\)
题目大意
int cnt=0;
int solve(int n)
{
if(n==0){cnt++;return cnt-1;}
int ls=solve(n-1);int rs=solve(n-1);
adde(ls,rs);return ls;
}
若运行 \(solve(n)\) ,可以得到一棵有 \(2^n\) 个点的树,树的节点从 \(0\) 开始编号。
给定 \(q\) 次询问,每次询问有两个值 \(x,d\) ,查询在这棵树上与节点 \(x\) 距离为 \(d\) 的点的数量。
分析
观察这棵树,发现了几个性质:
-
节点 \(u\) 的父亲节点为 \(u-lowbit(u)\) 。
-
节点 \(u\) 的深度是 \(u\) 的二进制表示中 \(1\) 的个数。
考虑计数,从 \(u\) 开始往上跳,每次跳统计子树内的答案。
发现在子树内距离为 \(d\) 等价于在 \(u\) 二进制表示中最靠右的 \(1\) 后添 \(1\) ,设为 \(v\),则用组合数表示即为 \(C_{v}^{d}\)
发现跳到祖先之后不能重复走子树,考虑容斥掉这些情况,那我们就钦定其走进了子树,减掉这一部分的贡献即可。
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e7+10,MOD=998244353;
const int Mxdt=100000;
inline char gc(){
static char buf[Mxdt],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,Mxdt,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
int t=0,f=0;char v=gc();
while(v<'0')f|=(v=='-'),v=gc();
while(v>='0')t=(t<<3)+(t<<1)+v-48,v=gc();
return f?-t:t;
}
int inv_fac[N];
int n,q,d,k,ans;
int v[N];
int fac[N];
inline int power(int x,int k)
{
int res=1;
while(k){
if(k&1) res=res*x%MOD;
k>>=1,x=x*x%MOD;
}
return res;
}
inline void initial()
{
fac[0]=inv_fac[0]=1;
for(register int i=1;i<=N-10;i++) fac[i]=fac[i-1]*i%MOD;
inv_fac[N-10]=power(fac[N-10],MOD-2);
for(register int i=N-11;i>=1;i--) inv_fac[i]=inv_fac[i+1]*(i+1)%MOD;
}
inline int C(int x,int y)
{
if(y<0) return 0;
if(x<y) return 0;
return fac[x]*inv_fac[y]%MOD*inv_fac[x-y]%MOD;
}
inline void Solve(int p,int val,int last)
{
if(p>k+1) return;
ans=(((ans+C(v[p],d-val))%MOD-C(last,d-val-1))%MOD+MOD)%MOD;
Solve(p+1,val+1,v[p]);
}
signed main()
{
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
initial();
n=read(),q=read();
while(q--){
ans=0;
k=read();
for(register int i=1;i<=k;i++) v[i]=read();
v[k+1]=n;
d=read();
Solve(1,0,-1);
printf("%lld\n",ans);
}
return 0;
}