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);
}
posted @ 2023-08-09 13:49  383494  阅读(11)  评论(0编辑  收藏  举报