题意
一颗无穷个节点的完全二叉树,编号满足线段树分配
求有多少条树上的简单路径编号和为 s s
考虑一条链
考虑从节点开始走的一条节点个数是 h h 的链
假设一直往左子树里走,那么他的贡献是
若链从下往上的第 i∈[1,h) i ∈ [ 1 , h ) 个点是右儿子则会给标号和带来独立的
注意到 x x 的取值只能是
证明:假设取 x−1 x − 1 ,这条长 h h 的链全部往右子树里走的贡献是
所以我们可以枚举 h∈[1,log2s], h ∈ [ 1 , log 2 s ] , 并算出 x, x , 令 ret=s− r e t = s − 上面的贡献
然后我们可以枚举看看能不能把某些点换成右儿子
即从大到小贪心用 2h−1−1,…,3,1 2 h − 1 − 1 , … , 3 , 1 凑出 ret r e t
考虑有分叉的情况
假设路径的 lca l c a 是 x x ,设从左儿子开始向下的链长度为 h1 h 1 ,从x右儿子开始向下的链长度为 h2 h 2 ,枚举 h1,h2∈[1,log2s] h 1 , h 2 ∈ [ 1 , log 2 s ]
假设这两条链都往左边走的贡献是
同理 x x 的位置也是唯一确定的,令上面的贡献
同样的我们可以考虑怎么用 1,3,…,2h1−1,1,3,…,2h2−1 1 , 3 , … , 2 h 1 − 1 , 1 , 3 , … , 2 h 2 − 1 凑出 ret r e t
这样显然很难做,我们考虑用 2,22,…,2h1,2,22,…,2h2 2 , 2 2 , … , 2 h 1 , 2 , 2 2 , … , 2 h 2 来凑
我们可以枚举选了 n n 个数,看看能不能用上面的数凑出
这个可以用 O(h1h2) O ( h 1 h 2 ) 的时间用数位 DP D P 来求出
数位 DP D P
设 f[i][j][k] f [ i ] [ j ] [ k ] 表示前 i+1 i + 1 位(因为待选数里没有1)选了 j j 个数,这一位是否进位的方案数
对于每一位枚举和 2,22,…,2h2 2 , 2 2 , … , 2 h 2 中的这一位填不填
注意到因为是第 i+1 i + 1 位如果 i>h1 i > h 1 那么这一位是不能选 1 1 的,另一边同理
假设中这一位选了 a∈[0,1] a ∈ [ 0 , 1 ] ,另外一个选了 b∈[0,1] b ∈ [ 0 , 1 ]
转移要满足 (a+b+k)%2=ret+n2i ( a + b + k ) % 2 = r e t + n 2 i & 1 1 的
具体实现以及卡常可以再看看代码思考一下
时间复杂度 O(log5s) O ( log 5 s )
#include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
const int N=52;
typedef long long ll;
int m,p;ll n,ans,mi[N],f[N][2*N][2];
inline ll sub(ll a,ll b){return a>=b?a-b:a;}
inline ll calc(ll s,ll q,int a,int b,int t){
memset(f[p],0,sizeof f[p]);f[p][0][0]=1;
fp(i,1,log2(s)+1){
int d=(s>>i)&1;p^=1;memset(f[p],0,sizeof f[p]);
fp(j,0,2*i-2)fp(k,0,1)if(f[p^1][j][k])
fp(x,0,1)if(!x||i<a)fp(y,0,1)if(!y||i<b)
if((k+x+y)%2==d)f[p][j+x+y][(k+x+y)/2]+=f[p^1][j][k];
}
return f[p][t][0];
}
int main(){
#ifndef ONLINE_JUDGE
file("s");
#endif
scanf("%lld",&n);mi[0]=1;ll x,ret;
while(mi[m-1]<=n)mi[m+1]=mi[m]<<1,++m;--m;
fp(i,1,m)if((x=n/(mi[i]-1))>0){
ret=n-x*(mi[i]-1);
fd(j,i-1,0)ret=sub(ret,mi[j]-1);
if(!ret)++ans;
}
fp(i,1,m)for(int j=1;mi[j]-1<=n;++j)
if((x=(n-mi[j]+1)/(mi[i+1]+mi[j+1]-3))>0){
ret=(n-mi[j]+1)-x*(mi[i+1]+mi[j+1]-3);
if(!ret){++ans;continue;}
if(i==1&&j==1){ans+=ret==5*x+1;continue;}
fp(k,1,i+j)if(~(ret+k)&1)
ans+=calc(ret+k,x,i,j,k);
}
printf("%lld",ans);
return 0;
}