题目
题目概要
对于
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,…,p2k−3⟩,有
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=0∑m−1pa+id
约定:该二叉树满足点 x ( 1 ⩽ x < 2 k − 1 ) x\;(1\leqslant x<2^{k-1}) x(1⩽x<2k−1) 的左儿子是编号为 2 x 2x 2x 的节点,右儿子是 ( 2 x + 1 ) (2x+1) (2x+1) 号节点。
数据范围与提示
k
⩽
30
k\leqslant 30
k⩽30 且
1
⩽
a
1\leqslant a
1⩽a 且
a
+
(
m
−
1
)
d
⩽
2
k
−
3
a+(m{\rm-}1)d\leqslant 2^{k}-3
a+(m−1)d⩽2k−3 。询问次数
q
⩽
300
q\leqslant 300
q⩽300 。
思路
首先考虑 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,k−1(2x)+fa+1−2k−1,k−1(2x+1)+[2k−1≡a]⋅⌊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(k⋅2k/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
*/