Confused? Read the quick-start guide.
Small input
9 points
Solve B-small
You may try multiple times, with penalties for wrong submissions.
Large input
12 points
You must solve the small input first.
You have 8 minutes to solve 1 input file. (Judged after contest.)
Problem
Consider an infinite complete binary tree where the root node is 1/1 and left and right childs of node p/q are p/(p+q) and (p+q)/q, respectively. This tree looks like:
1/1
______|______
| |
1/2 2/1
___|___ ___|___
| | | |
1/3 3/2 2/3 3/1
...
It is known that every positive rational number appears exactly once in this tree. A level-order traversal of the tree results in the following array:
1/1, 1/2, 2/1, 1/3, 3/2, 2/3, 3/1, ...
Please solve the following two questions:
Find the n-th element of the array, where n starts from 1. For example, for the input 2, the correct output is 1/2.
Given p/q, find its position in the array. As an example, the input 1/2 results in the output 2.
Input
The first line of the input gives the number of test cases, T. T test cases follow. Each test case consists of one line. The line contains a problem id (1 or 2) and one or two additional integers:
If the problem id is 1, then only one integer n is given, and you are expected to find the n-th element of the array.
If the problem id is 2, then two integers p and q are given, and you are expected to find the position of p/q in the array.
Output
For each test case:
If the problem id is 1, then output one line containing "Case #x: p q", where x is the case number (starting from 1), and p, q are numerator and denominator of the asked array element, respectively.
If the problem id is 2, then output one line containing "Case #x: n", where x is the case number (starting from 1), and n is the position of the given number.
Limits
1 ≤ T ≤ 100; p and q are relatively prime.
Small dataset
1 ≤ n, p, q ≤ 216-1; p/q is an element in a tree with level number ≤ 16.
Large dataset
1 ≤ n, p, q ≤ 264-1; p/q is an element in a tree with level number ≤ 64.
Sample
Input
Output
4
1 2
2 1 2
1 5
2 3 2
Case #1: 1 2
Case #2: 2
Case #3: 3 2
Case #4: 5
问题1解析:
对于第一种形式,即给定p和q,要确定该分式所在分式树的层序遍历序列中的位置问题。从题目的描述中,我们知道,当p>q时,该分式所代表的节点为其父节点的右孩子,且其父节点的值为(p-q)/q;当p<q时,该分式所代表的节点为其父节点的做孩子节点,且其父节点的值为p/(q-p);当p==q时,该分式代表的节点为分式树的根节点,且p==q==1。为了求一个分式在分式树的层序遍历的序列中的位置,我们可以通过两个步骤来求解:
首先求出该分式节点所在的层数,设为n(根节点为第0层),那么在分式树中,所有层数比该节点的低的节点个数为2^n-1个;
然后与该分式节点在同一层的节点中,排在该分式节点前的节点个数,这里要分为两种情况来分别讨论:
1.该分式节点在分式树的左子树中
那么我们从该节点不断的往根节点方向走,并记录走过的节点数。直到我们第一次碰到p==1的那个节点停止,假设此时走过的节点数为m,那么与该分式节点在同一层,且在该节点之前的节点个数为2^m-1(该分式节点为其父节点的右孩子)或2^m-2(该分式节点为其父节点的左孩子);
2.该分式节点在分式树的右子树中
同样我们从该节点往根节点的方向遍历,并记录走过的节点数,知道我们第一次碰到q==1的那个节点停止,假设此时走过的节点数为m,那么与该分式节点在同一层,且在该节点之前的节点个数为2^n-2^m(该节点为其父节点的左孩子)或2^n-2^m+1(该节点为其父节点的右孩子)。
在以上两种情况中,我们均需要考虑一种特殊情况,即p==1或q==1的情况,此时当前节点即为边界节点。在情况1中,在处于同一层且在该节点之前的节点个数为0,;情况2中,处于同一层且在该节点的前面的节点个数为2^n-1.
经过上面两步求得比该节点的层数低的节点个数和与该节点在同一层且在其前面的节点之后便得到了该节点在层序遍历序列中的位置了。
具体细节参考如下代码实现:
/*
*获取指定值所在的位置
*/
int getPosition(int p, int q)
{
if(p <= 0 || q <= 0) return -1;
if(p == 1 && q == 1) return 1; //为根节点
bool onLeft = false; //标识该节点是否在左子树中
int m = 0; //当前节点到最边缘节点的距离
int n = 0; //当前节点到根节点的距离,即该节点的深度
bool reachEdge = false; //标识往上遍历过程中,是否已经遍历过了边缘节点
bool isEdge = false;
if(p == 1 || q == 1) isEdge = reachEdge = true;
if(p == 1) onLeft = true;
bool isLeftChild = (p>q)?false:true; //标识当前节点是否为父节点的左孩子
while(p != 1 || q != 1)
{
n++;
if(!reachEdge) m++;
if(p > q) //当前节点为父节点的右孩子
{
p -= q; //往上得到父节点的值
if(!reachEdge && p == 1) //到达左边界节点
{
reachEdge = true;
onLeft = true;
}//if(!reachEdge && p == 1)
}//if(p>q)
else //当前节点为父节点的左孩子
{
q -= p;
if(!reachEdge && q == 1) //到达右边界节点
{
reachEdge = true;
onLeft = false;
}//if(!reachEdge && q == 1)
}//else
}//while
if(onLeft) //节点为左孩子节点
{
if(isEdge) return (1<<n);
if(isLeftChild) return (1<<n) + (1<<m)-2;
return (1<<n) + (1<<m) - 1;
}//if
else
{
if(isEdge) return (1<<(n+1))-1;
if(isLeftChild) return (1<<(n+1)) - (1<<m);
return (1<<(n+1)) - (1<<m) + 1;
}//else
return -1;
}//getPosition
复杂度分析:
通过前面的分析,我们知道,在求解过程中我们需要遍历的节点的个数为当前节点的深度,一次时间复杂度为O(lgn)。
问题2解析:
对于第2个问题的求解可以通过剪枝方法来求解,步骤如下:
1.求出第n个节点在分式树中的深度,假设为level;初始时以根节点p = q = 1作为根节点开始
2.判断当前节点在分式树的左子树还是右子树中,若在左子树则q = p + q;否则p = p + q。
3.剪枝:当节点在左子树中时,我们将根节点即其右子树裁剪掉,并以当前根节点的左孩子作为新的根节点;当节点在右子树时,我们将根节点及其左子树裁剪掉,并以当前根节点的右孩子作为新的根节点;
4.在新的子树中,当前节点的深度level减小了1,此时回到第2步,知道level = 0时,p和q的值即为第n个位置上的值
具体实现细节参考如下代码:
/*
*获取指定位置上的值
*/
void getValueOnPosition(int n, int& p, int& q)
{
p = 1, q = 1;
if(n == 1) return;
int level = 0;
while((1<<level)-1 < n) level++; //获取第n个位置所在的层数
level--;
int tmp = 0;
while(level > 0)
{
tmp = n - ((1<<level)-1);
if(0 <= tmp && tmp <= (1<<(level-1))) //目标节点在目标层的左半部分
{
q += p;
n -= 1<<(level-1);
}
else //目标节点在目标层的右半部分
{
p += q;
n -= 1<<level;
}//else
level--;
}//while
}//getValueOnPosition
复杂度分析:
在求解过程中,每次我们都裁剪掉一半的节点,while循环总共需要执行level遍,即时间复杂度为O(lgn)。