P9507 [BalkanOI2018] Popa 题解
题目描述
Ghiță 有一个下标从 \(0\) 开始的正整数序列 \(S\)。因为他是喀尔巴阡的国王,所以他想要构造一个节点编号为 \(0,1,\ldots ,N-1\) 的二叉树,满足:
- 树的中序遍历按节点编号升序排列。二叉树的中序遍历由以根的左子节点(如果存在)为根形成的子树的中序遍历,根的节点编号和以根的右子节点(如果存在)为根形成的子树的中序遍历顺次连接组成。
- 如果 \(x\) 是 \(y\) 节点的父亲,那么 \(S_x\) 整除 \(S_y\)。
二叉树是一种树形结构,每个节点最多有两个子节点,分别称为左子节点和右子节点。
不幸的是,著名的亡命之徒 Andrii Popa 偷走了序列 \(S\),Ghiță 不能直接获取到。但对于任意两个连续的子序列 \([a,b]\) 和 \([c,d]\),他可以使用最先进的技术(他的手机)求出 \(\gcd[a,b]\) 是否等于 \(\gcd [c,d]\),其中 \(\gcd[x,y]\) 指 \(S_x,S_{x+1},S_{x+2},\ldots ,S_y\) 的最大公因数。不幸的是,这项技术十分昂贵——如果 Ghiță 使用超过 \(Q\) 次,他将会支付一大笔罚金。请帮他在使用这项技术最多 \(Q\) 次的情况下构建出他想要的树。保证这是可能的。任何合法的构建方案都可以被接受。
数据范围
子任务编号 | 限制 | 分值 |
---|---|---|
\(1\) | \(N=100,Q=10^4\) | \(37\) |
\(2\) | \(N=10^3,Q=2\times 10^4\) | \(24\) |
\(3\) | \(N=10^3,Q=2\times 10^3\) | \(39\) |
思路
分析条件。
树的中序遍历按编号升序排列。
想到 BST。
如果 \(x\) 是 \(y\) 节点的父亲,那么 \(S_x\) 整除 \(S_y\)。
发现整除具有传递性,想到笛卡尔树。
\(O(n \log n)\) 做法
注意到树根一定是所有树的 \(\gcd\),可以二分树根所在的区间,\(\Theta(\log s)\) 次询问,其中 \(s\) 为区间长度。
找到树根后,两边递归建树。
namespace O_n_log_n{ // }{{{
int findroot(int l, int r){
while(r-l > 1){
int mid = (l+r)/2;
if(query(l, mid-1, l, r-1)){
r = mid;
} else {
l = mid;
}
}
return l;
}
int build(int l, int r, int *left, int *right){
if(l == r) return -1;
int rt = findroot(l, r);
left[rt] = build(l, rt, left, right);
right[rt] = build(rt+1, r, left, right);
return rt;
}
int solve(int in, int *left, int *right){
return build(0, in, left, right);
}
} // {}}}
\(\Theta(n)\) 做法
回忆笛卡尔树的 \(\Theta(n)\) 建树:维护目前全树的右链。
考虑右链末端点 \(u\),及将要加入树中的点 \(v\)。发现 \(\gcd(u,v)=v\),则 \(v\) 为 \(u\) 的祖先一定满足题意,类似笛卡尔树,将 \(u\) 出栈即可。
对于 \(u\) 出栈后的点 \(u'\),发现 \(u'\) 和 \(v\) 不相邻,但 \(u'\) 到 \(v\) 均为 \(u'\) 的后代,它们的 \(\gcd\) 为 \(u'\),因此询问 \(u'\) 到 \(v\) 的 \(\gcd\) 等价于询问 \(\gcd(u',v)\)。
namespace O_n{ // }{{{
std::stack<int> todo;
int solve(int in, int *left, int *right){
UP(i, 0, in){
left[i] = right[i] = -1;
if(todo.empty()){
todo.push(i);
} else {
while(!todo.empty()){
if(query(i, i, todo.top(), i)) {
left[i] = todo.top();
todo.pop();
} else break;
}
if(!todo.empty()){
right[todo.top()] = i;
}
todo.push(i);
}
}
int rt;
while(!todo.empty()){
rt = todo.top();
todo.pop();
}
return rt;
}
} // {}}}
完整代码:
#include <stack>
#define UP(i,s,e) for(auto i=s; i<e; ++i)
int query(int a, int b, int c, int d);
namespace solution{ // }{{{
constexpr int N = 1e3;
namespace O_n_log_n{ // }{{{
int findroot(int l, int r){
while(r-l > 1){
int mid = (l+r)/2;
if(query(l, mid-1, l, r-1)){
r = mid;
} else {
l = mid;
}
}
return l;
}
int build(int l, int r, int *left, int *right){
if(l == r) return -1;
int rt = findroot(l, r);
left[rt] = build(l, rt, left, right);
right[rt] = build(rt+1, r, left, right);
return rt;
}
int solve(int in, int *left, int *right){
return build(0, in, left, right);
}
} // {}}}
namespace O_n{ // }{{{
std::stack<int> todo;
int solve(int in, int *left, int *right){
UP(i, 0, in){
left[i] = right[i] = -1;
if(todo.empty()){
todo.push(i);
} else {
while(!todo.empty()){
if(query(i, i, todo.top(), i)) {
left[i] = todo.top();
todo.pop();
} else break;
}
if(!todo.empty()){
right[todo.top()] = i;
}
todo.push(i);
}
}
int rt;
while(!todo.empty()){
rt = todo.top();
todo.pop();
}
return rt;
}
} // {}}}
} // {}}}
int solve(int in, int *left, int *right){
return solution::O_n::solve(in, left, right);
}