[usOJ25705]编码

220 篇文章 2 订阅
98 篇文章 0 订阅

题目

传送门 to usOJ

题目概要
对于 k k k 层的完满二叉树,设其 prufer \text{prufer} prufer 序列为 ⟨ p 1 , p 2 , … , p 2 k − 3 ⟩ \langle p_1,p_2,\dots,p_{2^k-3}\rangle p1,p2,,p2k3,有 q q q 次询问,给出 a , d , m a,d,m a,d,m 请求出
∑ i = 0 m − 1 p a + i d \sum_{i=0}^{m-1}p_{a+id} i=0m1pa+id

约定:该二叉树满足点 x    ( 1 ⩽ x < 2 k − 1 ) x\;(1\leqslant x<2^{k-1}) x(1x<2k1) 的左儿子是编号为 2 x 2x 2x 的节点,右儿子是 ( 2 x + 1 ) (2x+1) (2x+1) 号节点。

数据范围与提示
k ⩽ 30 k\leqslant 30 k30 1 ⩽ a 1\leqslant a 1a a + ( m − 1 ) d ⩽ 2 k − 3 a+(m{\rm-}1)d\leqslant 2^{k}-3 a+(m1)d2k3 。询问次数 q ⩽ 300 q\leqslant 300 q300

思路

首先考虑 prufer \text{prufer} prufer 序列是怎么形成的。首先, 3 3 3 的子树中的所有叶子是编号最大的那些点,所以在别人选完之前,永远不会选它们。也就是要先去选 2 2 2 的子树中的叶子。

类似地,总是要先删掉左儿子子树中的叶子。而 2 2 2 的左子树的叶子删完之后, 2 2 2 还不是叶子;于是只能删除 2 2 2 的右儿子子树中的叶子。最终删掉 2 2 2

1 1 1 此时就会被立刻删除。然后对 3 3 3 的处理和对 1 1 1 的处理是类似的。于是我们看出这里有两种处理模式:

  • 后序遍历,比如节点 2 2 2:先删除左儿子子树中所有叶子(使用后序遍历),再删除右儿子子树中所有叶子(后序遍历),最后删除自己。
  • 中序遍历,比如节点 1 1 1:先删除左儿子子树中所有叶子(后序遍历),再删除自己,最后删除右儿子子树中所有叶子(中序遍历)。

现在,一个恐怖的事情出现了:一个完满二叉树,节点编号关于根节点编号是 线性变换。所以,当根节点为 x x x 时,这个答案一定是 k x + b kx+b kx+b,毫无疑问。

于是考虑 d p \tt dp dp 求解。设 f a , k f_{a,k} fa,k k k k 层完满二叉树在后序遍历时 ∑ t p a + t d \sum_{t}p_{a+td} tpa+td 关于 x x x 的函数。显然有转移
f a , k ( x ) = f a , k − 1 ( 2 x ) + f a + 1 − 2 k − 1 , k − 1 ( 2 x + 1 ) + [ 2 k − 1 ≡ a ] ⋅ ⌊ x 2 ⌋ f_{a,k}(x)=f_{a,k-1}(2x)+f_{a+1-2^{k-1},k-1}(2x+1)+\big[2^k{\rm-}1\equiv a\big]\cdot\left\lfloor\frac{x}{2}\right\rfloor fa,k(x)=fa,k1(2x)+fa+12k1,k1(2x+1)+[2k1a]2x
出现了一个 ⌊ x 2 ⌋ \lfloor\frac{x}{2}\rfloor 2x,感觉很不妙啊。所以可以考虑分别定义 f l , f r fl,fr fl,fr 为左儿子(即根节点是 2 x 2x 2x 时,关于 x x x 的函数)和右儿子(即根节点为 ( 2 x + 1 ) (2x+1) (2x+1) 时)的值,利于转移。

预处理是 O ( k d ) \mathcal O(kd) O(kd) 的。查询时前缀和作差;求前缀和时,如果包含左儿子,那就直接用 d p \tt dp dp 值计算,然后往右儿子递归,否则往左儿子递归。复杂度 O ( k ) \mathcal O(k) O(k)

显然 d d d 可以很大,此时上面的方法行不通。然而这时 m m m 便很小了!就可以暴力查询单点值了!这是 O ( k m ) \mathcal O(km) O(km) 的!

最终复杂度 O ( k d + k m ) ⩾ O ( k ⋅ 2 k / 2 ) \mathcal O(kd+km)\geqslant\mathcal O(k\cdot 2^{k/2}) O(kd+km)O(k2k/2),可以通过。

代码

只要有空我一定来重构;原来的这个真调不出来了

#include <cstdio>
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||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;
}

const int MaxN = 32;

/**
 * @param tag 0 则为左儿子(后序),1 则为右儿子(中序)
 * @return 单点查询的结果
 **/
int query(int qid,int o,int l,int r,bool tag){
	int m = (r-l+1)/2+l-1; // 分水岭
	if(tag == false){
		if(qid == r) return o>>1;
		if(qid <= m) return query
			(qid,o<<1,l,m,false);
		return query(qid,o<<1|1,m+1,r-1,tag);
	}
	if(tag == true){
		if(qid == m+1) return o<<1|1;
		if(qid <= m) return query
			(qid,o<<1,l,m,false);
		return query(qid,o<<1|1,m+2,r,tag);
	}
	return puts("WDNMD");
}

struct Function{
	int_ k, b; // kx + b
	Function(){ }
	Function(int_ K,int_ B){
		k = K, b = B;
	}
	int_ operator()(const int_ &x) const {
		return k*x+b; // 函数的取值
	}
	Function operator + (const Function &t) const {
		return Function(k+t.k,b+t.b);
	}
};
/* 子树大小为 2^x-1 时,答案是 k*(x/2)+b 。仅用于后序。 */
Function dp[MaxN][1<<(MaxN/2)];

/**
 * @param m 目前的模数
 **/
void shougYun(int k,int m){
	for(int i=0; i<m; ++i)
		dp[0][i] = Function(0,0);
	for(int i=1; i<=k; ++i){
		int half = (1<<i>>1)-1; // 左子树大小
		for(int j=0; j<m; ++j){
			dp[i][j] = dp[i-1][((j-half)%m+m)%m];
			dp[i][j].b += dp[i][j].k>>1;
			dp[i][j] = dp[i][j]+dp[i-1][j];
			dp[i][j].k <<= 1; // 升格为 x/2
		}
		++ dp[i][((1<<i)-1)%m].k; // 自己被删了
	}
	for(int i=1; i<=k; ++i) for(int j=0; j<m; ++j)
		printf("dp[%d,%d] = %d*x+%d\n",i,j,dp[i][j].k,dp[i][j].b);
// printf("dp[2][0] = %lld*x + %lld\n",dp[2][0].k,dp[2][0].b);
}
/**
 * @param r current range is [ @p l(ignored) , @p r ]
 * @param want want @b RELATIVE id equiv @p want
 * @param m current modulo
 **/
int_ mumuMei(int o,int r,int R,int k,int want,int m,bool tag){
	if(k == 0) // 空子树
		return 0; // 啥也没有
	if(r <= R && !tag){ // 被完全包含
		printf("get out o = %d\n",o);
		int_ res = dp[k][want](o>>1);
		res += (o&1)*dp[k][want].k>>1;
		printf("res = %lld\n",res);
		return res; // dirrectly calculate
	}
	int mid = (1<<k>>1)-1; // 一半的大小
	if(tag == false){
		int res = mumuMei(o<<1,r-1-mid,
			R,k-1,want,m,false);
		want -= mid; // 去掉左子树
		want = (want%m+m)%m;
		if(r-mid <= R) // 右边剩点
			res += mumuMei(o<<1|1,r-1,
				R,k-1,want,m,tag);
		return res;
	}
	if(tag == true){
		int res = mumuMei(o<<1,r-1-mid,
			R,k-1,want,m,false);
		want = ((want-mid)%m+m)%m;
		if(1 == want && r-mid <= R) // 不愧是我
			res += o<<1|1;
		want = (want+m-1)%m;
		if(r-mid+1 <= R) // 右边剩点
			res += mumuMei(o<<1|1,r,
				R,k-1,want,m,tag);
		return res;
	}
	return puts("Damn shit");
}

int main(){
	int k = readint(), q = readint();
	for(int i=1,a,d,m; i<=q; ++i){
		a = readint(), d = readint();
		m = readint(); int_ ans = 0;
		if(d <= (1<<(k>>1))){ // test
			shougYun(k,d); // 对 d 取模
			ans = mumuMei(1,(1<<k)-1,a+m*d-1,k,a%d,d,1);
			printf("ans = %lld\n",ans);
			ans -= mumuMei(1,(1<<k)-1,a-1,k,a%d,d,1);
			printf("ans = %lld\n",ans);
		}
		else{ // 可接受
			for(int j=0; j<m; ++j)
				ans += query(a+j*d,1,1,(1<<k)-1,1);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

/*

若 k 为奇,则含义为 (k-1)/2 个自己 + 1 个父亲
= (k-1)/2*(2x+1) + x = k*x+(k-1)/2

若 k 为偶,含义为 k/2 个自己
= k/2*(2x+1) = k*x+k/2

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值