[CF750G]New Year and Binary Tree Paths

98 篇文章 0 订阅
30 篇文章 0 订阅

题目

传送门 to CF

题意概要
对于一棵无限大的完全二叉树,节点 x x x 的左儿子为 2 x 2x 2x,右儿子为 2 x + 1 2x+1 2x+1

请求出有多少个点对 a , b    ( a ⩽ b ) a,b\;(a\leqslant b) a,b(ab) 满足 a , b a,b a,b 的树上路径的点编号之和为 s s s

数据范围与提示
s ⩽ 1 0 15 s\leqslant 10^{15} s1015

思路

从一个 s i m p l e    v e r s i o n \rm simple\;version simpleversion 想起——只有一条链。

很容易发现,一个长度为 h h h 的链,以 x x x 作为上端点,点权和最小是 ∑ j = 0 h − 1 2 j x = ( 2 h − 1 ) ⋅ x \sum_{j=0}^{h-1}2^jx=(2^h-1)\cdot x j=0h12jx=(2h1)x,即一直往左儿子走的情况。

显然 x x x 为上端点的任意情况都劣于 x + 1 x+1 x+1 为上端点的情况。所以实际上 每个上端点对应的点权和区间是不交的

如果要具体一点,就是设 t i = 1 t_i=1 ti=1 表示自底向上第 i i i 个位置选择向右走。那么这个选择提供的权值变化是 ∑ j = 0 i − 1 2 j = 2 i − 1 \sum_{j=0}^{i-1}2^j=2^i-1 j=0i12j=2i1,因为第 j    ( j ⩽ i ) j\;(j\leqslant i) j(ji) 个位置会被迫移动 2 i − j 2^{i-j} 2ij 个位置。此时即使取 t t t 全部为 1 1 1 也不能达到 ( x + 1 ) (x+1) (x+1) 对应的最小点权和。

类似地,如果 x x x a , b a,b a,b l c a \rm lca lca,两边的长度分别为 l , r l,r l,r,那么点权和最小是 ( 2 l − 1 + 2 r − 1 − 1 ) ⋅ x + ( 2 r − 1 − 1 ) (2^l-1+2^r-1-1)\cdot x+(2^{r-1}-1) (2l1+2r11)x+(2r11),即两条路径的权值和相加,减去 x x x 重复计算了一次。你会发现它还是满足 区间不交 的性质……

所以枚举 l , r l,r l,r 之后, x x x唯一的。其次只需要求出两边对应的 t t t 序列即可,类似于一个背包问题。背包容量很大,怎么办?考虑 枚举个数 来填补 − 1 -1 1,这样物品权值变为 2 i 2^i 2i 便可以直接按照 s s s 的要求选择。

枚举 l , r l,r l,r 和个数,做一个数位 d p \tt dp dp,三个维度分别是考虑到第几位、已选个数、进位(零或一)。时间复杂度 O ( log ⁡ 5 s ) \mathcal O(\log^5s) O(log5s),非常离谱的复杂度。实际运行非常快,可以通过。

有一个优化。枚举个数对目标 s s s 的影响很小,即 s ′ = s + c n t s'=s+cnt s=s+cnt 在前 log ⁡ log ⁡ s \log\log s loglogs 位可能有明显的变化,但是后面至多变化一次。那么我们可以从后往前做 d p \tt dp dp,定义不变。后面的二进制位如果不变,就完全不必重新求 d p \tt dp dp 数组。此时复杂度是 O ( log ⁡ 4 s log ⁡ log ⁡ s ) \mathcal O(\log^4s\log\log s) O(log4sloglogs),稍微靠谱一点。

代码

当然并没有加上那个优化。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; '0'>c||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MAXN = 105;
int_ dp[2][MAXN][2];

const int coe[3][3] = {{1,0,0},{1,1,0},{1,2,1}};
int main(){
	long long s; scanf("%lld",&s);
	int n = 1; while(s>>n) ++ n;
	int_ ans = 0; // int_ needed
	rep(l,1,n) rep(r,1,n){
		int_ k = s-(1ll<<r>>1)+1;
		int_ x = k/((1ll<<l)+(1ll<<r)-3);
		if(x <= 0) continue; // invalid
		k -= x*((1ll<<l)+(1ll<<r)-3);
		const int max_cnt = (l == 1 ? 0 : (l-2)) + (r == 1 ? 0 : (r-2));
		if(k+max_cnt < 0) continue; // not achievable
		int cnt = static_cast<int>((k > 0) ? 0 : (-k));
		for(k+=cnt; cnt<=max_cnt; ++cnt,++k){
			if(k&1) continue; // no way to fix it
			if(!k){ ++ ans; continue; } // choose nothing
			int len = 0; while(k>>len) ++ len;
			dp[0][0][0] = 1, dp[0][0][1] = 0;
			int now = 0, lst; // count of choices
			for(int i=1,fr=0; i<len; ++i,fr^=1){
				lst = now; now += (i <= l-2) + (i <= r-2);
				memset(dp[i&1],0,(now+1)*2<<3);
				rep(j,0,lst) rep(d,0,1) rep(a,0,now-lst)
					if((d^a^(k>>i)^1)&1) // match bit
						dp[i&1][j+a][(d+a)>>1] +=
							coe[now-lst][a]*dp[fr][j][d];
			}
			if(now >= cnt) // achievable
				ans += dp[(len&1)^1][cnt][0];
		}
	}
	printf("%lld\n",ans);
	return 0;
}

后记

这个 区间不交 的性质真的神奇。如果非要说原因的话——权值是关于 x x x 的一次函数。就像这道题就是直接维护函数。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值