[CodeForces750G]New Year and Binary Tree Paths

题意

一颗无穷个节点的完全二叉树,编号满足线段树分配

求有多少条树上的简单路径编号和为 s s


考虑一条链

考虑从节点x开始走的一条节点个数是 h h 的链

假设一直往左子树里走,那么他的贡献是

xi=0h12i=(2h1)x

若链从下往上的第 i[1,h) i ∈ [ 1 , h ) 个点是右儿子则会给标号和带来独立的

j=0i12j=2i1 ∑ j = 0 i − 1 2 j = 2 i − 1
的贡献

注意到 x x 的取值只能是s2h1

证明:假设取 x1 x − 1 ,这条长 h h 的链全部往右子树里走的贡献是

(2h1)(x1)+i=1h12i1=2hh2h1

所以我们可以枚举 h[1,log2s], h ∈ [ 1 , log 2 ⁡ s ] , 并算出 x, x , ret=s r e t = s − 上面的贡献

然后我们可以枚举看看能不能把某些点换成右儿子

即从大到小贪心用 2h11,,3,1 2 h − 1 − 1 , … , 3 , 1 凑出 ret r e t

考虑有分叉的情况

假设路径的 lca l c a x x ,设从x左儿子开始向下的链长度为 h1 h 1 ,从x右儿子开始向下的链长度为 h2 h 2 ,枚举 h1,h2[1,log2s] h 1 , h 2 ∈ [ 1 , log 2 ⁡ s ]

假设这两条链都往左边走的贡献是

=2x(2h11)+(2x+1)(2h21)+2h21(2h1+1+2h2+13)x+2h21(5)(6) (5) 2 x ( 2 h 1 − 1 ) + ( 2 x + 1 ) ( 2 h 2 − 1 ) + 2 h 2 − 1 (6) = ( 2 h 1 + 1 + 2 h 2 + 1 − 3 ) x + 2 h 2 − 1

同理 x x 的位置也是唯一确定的,令ret=s上面的贡献

同样的我们可以考虑怎么用 1,3,,2h11,1,3,,2h21 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 个数,看看能不能用上面的数凑出ret+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,,2h1 2,22,,2h2 2 , 2 2 , … , 2 h 2 中的这一位填不填

注意到因为是第 i+1 i + 1 位如果 i>h1 i > h 1 那么这一位是不能选 1 1 的,另一边同理

假设2,22,,2h1中这一位选了 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

f[i+1][j+a+b][a+b+k2]=f[i][j][k]

具体实现以及卡常可以再看看代码思考一下

时间复杂度 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值