[模拟赛]tree

\(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\) 的点的数量。

分析

[模拟赛]tree

观察这棵树,发现了几个性质:

  • 节点 \(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;
} 
上一篇:默认让UniDBTreeGrid展开一级节点


下一篇:牛客竞赛数学专题班简单排列和组合(排列组合问题、阶乘、组合数)