CF750G New Year and Binary Tree Paths(数位dp二进制+数学)

CF750G New Year and Binary Tree Paths

description

题目链接

一颗无穷个节点的完全二叉树。

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

s ≤ 1 e 15 s\le 1e15 s1e15

solution

  • 一条单链的情况

    考虑从节点 x x x开始走一条节点个数是 h h h的链(链长为 h − 1 h-1 h1

    假设从上往下一直往左子树走【 x , 2 x , 4 x , 8 x . . . x,2x,4x,8x... x,2x,4x,8x...

    则贡献为 x ∑ i = 0 h − 1 2 i = ( 2 h − 1 ) x x\sum_{i=0}^{h-1}2^i=(2^h-1)x xi=0h12i=(2h1)x

    若链从下往上的第 i ∈ [ 1 , h ) i\in[1,h) i[1,h)个点是右儿子,则会给标号和带来独立的 ∑ j = 0 i − 1 2 j = 2 i − 1 \sum_{j=0}^{i-1}2^j=2^i-1 j=0i12j=2i1贡献

    可以解出, x x x的取值只能是 ⌊ s 2 h − 1 ⌋ \lfloor\frac{s}{2^h-1}\rfloor 2h1s

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

    ( 2 h − 1 ) ( x − 1 ) + ∑ i = 1 h − 1 ( 2 i − 1 ) = 2 h x − x − 2 h + 1 + 2 h − 1 − h = ( 2 h − 1 ) x − h < ( 2 h − 1 ) x (2^h-1)(x-1)+\sum_{i=1}^{h-1}(2^i-1)=2^hx-x-2^h+1+2^h-1-h=(2^h-1)x-h< (2^h-1)x (2h1)(x1)+i=1h1(2i1)=2hxx2h+1+2h1h=(2h1)xh<(2h1)x

    这已经是最大的不是选 x x x产生的可能贡献了,所以只能是 x x x

    这里同样经典的,可以判断能否用【 1 , 3 , . . . , 2 h − 1 − 1 1,3,...,2^{h-1}-1 1,3,...,2h11】构造出 s − ( 2 h − 1 ) x s-(2^h-1)x s(2h1)x,先用大的开始尝试(从最大的往小的开始一个一个试,有点类似倍增的思想不断逼近直到相等的感觉)

  • 分叉情况(两条子链在根节点拼接)

    假设根节点为 x x x,设 x x x左儿子开始向下的链长度为 h 1 h_1 h1,右儿子开始向下的链长度为 h 2 h_2 h2,枚举 h 1 , h 2 ∈ [ 1 , log 2 s ] h_1,h_2\in[1,\text{log}_2^s] h1,h2[1,log2s]

    假设这两条链都往各自的左儿子走,贡献是:

    x + 2 x ( 2 h 1 − 1 ) + ( 2 x + 1 ) ( 2 h 2 − 1 ) = ( 2 h 1 + 1 + 2 h 2 + 1 − 3 ) x + 2 h 2 − 1 x+2x(2^{h_1}-1)+(2x+1)(2^{h_2}-1)=(2^{h_1+1}+2^{h_2+1}-3)x+2^{h_2}-1 x+2x(2h11)+(2x+1)(2h21)=(2h1+1+2h2+13)x+2h21

    同理, x x x的位置也是唯一确定的,即 ⌊ s − ( 2 h 2 − 1 ) 2 h 1 + 1 + 2 h 2 + 1 − 3 ⌋ \lfloor\frac{s-(2^{h_2}-1)}{2^{h_1+1}+2^{h_2+1}-3}\rfloor 2h1+1+2h2+13s(2h21)

    t = s t=s t=s减去上面的贡献

    同时,考虑怎么用各自某一层走右儿子产生的贡献【 1 , 3 , . . . , 2 h 1 − 1 ; 1 , 3 , . . . , 2 h 2 − 1 1,3,...,2^{h_1}-1;1,3,...,2^{h_2}-1 1,3,...,2h11;1,3,...,2h21】来构造出 t t t,转换一下考虑用【 2 , 2 2 , . . . , 2 h 1 ; 2 , 2 2 , . . . , 2 h 2 2,2^2,...,2^{h_1};2,2^2,...,2^{h_2} 2,22,...,2h1;2,22,...,2h2】来凑 t t t

    枚举选了 n n n个数,判断能不能用这些数凑出 t + n t+n t+n

    • 这里的实现考虑使用数位 d p dp dp O ( h 1 h 2 ) O(h_1h_2) O(h1h2)求出结果

      f i , j , k : f_{i,j,k}: fi,j,k: i i i h 1 h_1 h1 h 2 h_2 h2两条链的状态一共选了 j j j 2 2 2的次方【可能选了 2 2 2^2 22 2 i − 1 . . . 2^{i-1}... 2i1...,反正一共选了 j j j个】,是否有进位 k = 1 / 0 k=1/0 k=1/0的方案数

      对于每一位枚举 h 1 h_1 h1链和 h 2 h_2 h2链上的选择情况 s 1 , s 2 s_1,s_2 s1,s2

      注意到,如果 i = h 1 i=h_1 i=h1,这一位是不能选 1 1 1【表示进位】的, h 2 h_2 h2同理

      则可以得到转移方程: f [ i + 1 ] [ j + s 1 + s 2 ] [ s 1 + s 2 + k 2 ] = ∑ f [ i ] [ j ] [ k ] ( s 1 + s 2 + k ≡ ⌊ t + n 2 i ( m o d 2 ) ⌋ ) f[i+1][j+s_1+s_2][\frac{s_1+s_2+k}{2}]=\sum f[i][j][k]\quad\Big(s_1+s_2+k\equiv \lfloor\frac{t+n}{2^i}\pmod 2\rfloor\Big) f[i+1][j+s1+s2][2s1+s2+k]=f[i][j][k](s1+s2+k2it+n(mod2))

时间复杂度为 O ( log 5 s ) O(\text{log}^5s) O(log5s)

code

#include <cmath>
#include <cstdio>
#include <cstring>
#define maxn 60
#define int long long
int s, m, now;
int f[2][maxn << 1][2], mi[maxn];

int calc( int goal, int q, int h1, int h2, int cnt ) {
	memset( f[now], 0, sizeof( f[now] ) );
	f[now][0][0] = 1;
	for( int i = 1;i <= log2( goal ) + 1;i ++ ) {
		int d = goal >> i & 1;
		now ^= 1;
		memset( f[now], 0, sizeof( f[now] ) );
		for( int j = 0;j <= ( i - 1 << 1 );j ++ ) //到i-1及以前可以一共选了[0,(i-1)*2]个数 左儿子(h1)链的选择和右儿子(h2)链的选择 
			for( int k = 0;k <= 1;k ++ )
				if( f[now ^ 1][j][k] )
					for( int s1 = 0;s1 <= 1;s1 ++ )
						if( ! s1 or i < h1 )
							for( int s2 = 0;s2 <= 1;s2 ++ )
								if( ! s2 or i < h2 )
									if( ( k + s1 + s2 ) % 2 == d ) //判断同余 
										f[now][j + s1 + s2][( k + s1 + s2 ) / 2] += f[now ^ 1][j][k];
	}
	return f[now][cnt][0];
}

signed main() {
	int ans = 0;
	scanf( "%lld", &s );
	mi[0] = 1;
	for( int i = 1;i < 60;i ++ ) {
		mi[i] = mi[i - 1] << 1;
		if( mi[i] > s and mi[i - 1] <= s ) m = i;
	}
	for( int i = 1;i <= m;i ++ ) { //单链的构造 
		int x = s / ( mi[i] - 1 );
		if( x <= 0 ) continue;
		int t = s - x * ( mi[i] - 1 );
		for( int j = i - 1;~ j;j -- )
			if( t >= mi[j] - 1 ) t -= mi[j] - 1;
		if( ! t ) ans ++;
	}
	for( int h1 = 1;h1 <= m;h1 ++ )
		for( int h2 = 1;mi[h2] - 1 <= s;h2 ++ ) {
			int x = ( s - mi[h2] + 1 ) / ( mi[h1 + 1] + mi[h2 + 1] - 3 );
			if( x <= 0 ) continue;
			int t = ( s - mi[h2] + 1 ) - x * ( mi[h1 + 1] + mi[h2 + 1] - 3 );
			if( ! t ) {	ans ++; continue; }
			if( h1 == 1 and h2 == 1 ) { ans += ( t == 5 * x + 1 ); continue; } //x 2x 2x+1 -> 5x+1
			for( int n = 1;n <= h1 + h2;n ++ ) //枚举选了n个右儿子 
				if( ( ( t + n ) & 1 ) == 0 ) ans += calc( t + n, x, h1, h2, n );
		}
	printf( "%lld\n", ans );
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值