我考后整整写了 1 个多小时 …
sol:
考验强大的数学推演能力
我们把一个节点视作 01 序列,走左子树相当于在序列末尾添加 0 ,走右子树相当于添加 1 。
那么两个点的距离其实取决于公共前缀的长度。
所以我们想到枚举 lca ,同时根据完全二叉树的对称性可知相同深度的点对答案的贡献是一样的。
首先枚举 i = 0 ~ n-1 表示深度。
这里必须分两种情况计算:
- 两个点在同一子树内, 2 i ∗ 2 m = 2 i + m 2^{i} * 2^{m}=2^{i+m} 2i∗2m=2i+m
- 两个节点一个在左子树,一个在右子树,不难发现总数恒为 2 m − 2 2^{m-2} 2m−2 。
假设左子树深度为 i+k ,右子树深度为 i+(m-k) ,这里 1<=k<=m-1 。因为深度不能超过 n ,所以 i+k<=n 且 i+(m-k)<=n
解出 k 的范围即可 (利用数学表达式推式子)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int Maxn=3e6+5;
const int mod=998244353;
int n,m;
ll res,f[Maxn];
int main() {
f[0]=1;
for(int i=1;i<=3e6;i++) {
f[i]=f[i-1]*2%mod;
}
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) {
if(i+m<=n-1) {
res=(res+f[i+m])%mod;
}
if(m==1) continue;
int l=max(1,i+m-n+1),r=min(m-1,n-1-i);
if(l<=r) {
res=(res+(r-l+1)*f[i+m-2]%mod)%mod;
}
}
printf("%lld",(res*2)%mod);
}