九度无限完全二叉树的层次遍历

题目1543:无限完全二叉树的层次遍历

时间限制:1 秒

内存限制:128 兆

特殊判题:

提交:340

解决:46

题目描述:

有一棵无限完全二叉树,他的根节点是1/1,且任意一个节点p/q的左儿子节点和右儿子节点分别是,p/(p+q)和(p+q)/q。如下图:
九度无限完全二叉树的层次遍历_第1张图片
它的层次遍历结果如下:
1/1, 1/2, 2/1, 1/3, 3/2, 2/3, 3/1,...
有如下两类问题:
1.找到层次遍历的第n个数字。如,n为2时,该数字为1/2;
2.给定一个数字p/q,输出它在层次遍历中的顺序,如p/q为1/2时,其顺序为2;

输入:

输入包含多组测试用例,输入的第一行为一个整数T,代表共有的测试用例数。
接下去T行每行代表一个测试用例,每个测试用例有如下两种类型
1.1 n。输出层次遍历中,第n个数字。
2.2 p q。输出p/q在层次遍历中的顺序。
1 ≤ n, p, q ≤ 2^64-1

输出:

对于每个测试用例,若其类型为1,输出两个整数p q,代表层次遍历中第n个数字为p/q。
若其类型为2,输出一个整数n,代表整数p/q在层次遍历的中的顺序n。
数据保证输出在[1,2^64-1]范围内。

样例输入:
4
1 2
2 1 2
1 5
2 3 2
样例输出:
1 2
2
3 2
5	

思路:第一眼看到这个题目"按层遍历",联想起BFS,用一个队列来模拟,这样子就需要建立一棵二叉树,明显的空间复杂度不符合要求,但是不用结点指针的类型来模拟二叉树,还有什么办法可以做到呢?又联想到堆,堆的特点是A[1]是根,如果当前根是A[i],则左结点是A[i*2],右结点是A[i*2+1],知道这个特性,又如何呢?先看题目:

任意一个节点p/q的左儿子节点和右儿子节点分别是,p/(p+q)和(p+q)/q。

翻译上面这句话就是,

如果A[i]的键值是p/q,则A[left]=p/(p+q),即分子不变,分母q=p+q;(这是一个很重要的迭代式)

同样,A[right]=(p+q)/q,分母不变,分子p=p+q;(迭代式)

而我们已经知道当前根的键值A[1],p=1,q=1;

现在问题转化成,如果我们知道某一个位置n,要求A[n]的键值p/q,要怎么做?

答案是:我们只需要知道从1到n的路径,然后用上面的迭代式一直从根往下迭代即可.所以,首要任务,就是找到从根到这个结点的路径!

要找到从根到这个结点的路径其实也很容易,就是这个结点每次都除以2再向下取整即可得到父节点.路径可以用一个vector保存起来.知道路径之后,就可以根据上面思路写出代码.

题目还有一个要求,就是知道当前的键值p/q,如何找到这是第几个结点?

有了上面的思路,我们容易联想到,找到路径问题就会迎刃而解,观察可以知道,如果p>q,则这是右结点,否则这是左结点,根据这个也可以继续向上迭代了.

第一次代码:

#include<iostream>
#include<vector>
using namespace std;
typedef unsigned long long int64;
struct node
{
	int64 p;
	int64 q;
};
//vector<node> A;
vector<int64 > vec;
vector<node > vec2;
node no,y,x;
void find( node n)
{
	vec2.push_back(n);
	
	no.q=no.p=-1;
	while(1)
	{
		if( n.q ==1 && n.p ==1) break;
		if( n.q > n.p)
		{
			no.q=n.q-n.p;
			no.p=n.p;
			vec2.push_back(no);
		}
		else
		{
			no.q=n.q;
			no.p=n.p-n.q;
			vec2.push_back(no);
		}
		n=no;
	}
	int64 sum=1;
	for(int i=vec2.size()-2;i>=0;--i)
	{
		if( vec2[i].p > vec2[i].q )
		{
			sum = sum * 2+1;
		}
		else
		{
			sum = sum * 2;
		}
	}
	cout<<sum<<endl;
}
void set(int64 n)
{
	for(int64 i=n;i>=1;i /=2)
		vec.push_back(i);
	//A[1].q=A[1].p=1;
	
	y.q=y.p=x.p=x.q=1;
	for(int i=vec.size()-2 ; i>=0 ; --i )
	{
		if( (vec[i] & 1) ==1 )//奇数,右子树
		{
			x.p = y.p + y.q ;
			//A[vec[i+1]].p=A[vec[i]].p+A[vec[i]].q;
			x.q =  y.q ;
			//A[vec[i+1]].q=A[vec[i]].q;
		}
		else//左子树
		{
			x.p = y.p;
			//A[vec[i+1]].p=A[vec[i]].p;
			x.q = y.q + y.p ;
			//A[vec[i+1]].q = A[vec[i]].q+A[vec[i]].p;
		}
		y=x;
	}
	cout<<x.p<<" "<<x.q<<endl;
}
int main()
{
	int64 T,a,b,c;
	node n;

	cin>>T;
	while(T--)
	{
		cin>>a;
		if( a==1)
		{
			cin>>b;
			vec.clear();
			set(b);
		}
		else if(a==2)
		{
			//cout<<A[5].p<<A[5].q<<endl;
			vec2.clear();
			cin>>b>>c;
			if( b==c)
				continue;
			n.q=c;
			n.p=b;
			find(n);
		}
	}

}
上面的代码虽然可以AC,但是,看起来比较混乱,后来发现,将每个结点存起来似乎并不优化,因为这是二叉树,我们只需要存当前结点是左结点还是右结点即可,用一位就可以表示,空间上得到不少优化,而且看起来比较清晰.

优化后:

#include<iostream>
#include<vector>
using namespace std;

typedef unsigned long long int64;
vector<int64> path;

void FindTheNode(int64 x)
{
	int64 y;
	path.clear();
	while(x!=1)
	{
		y=x >> 1;
		if( (y<<1) == x) //left node
			path.push_back(1);  //1 means left
		else path.push_back(0); //0 means right
		x=y;
	}
	int64 p=1,q=1;
	for(int i=path.size()-1;i>=0;--i)
	{
		if( path[i] == 1)
			q=p+q;
		else
			p=p+q;
	}
	cout<<p<<' '<<q<<endl;
}
void FindTheNum(int64 p,int64 q)
{
	path.clear();
	while(p != q) //当不到根时,继续循环
	{
		if( p > q)
		{
			path.push_back(0);
			p = p-q;
		}
		else
		{
			path.push_back(1);
			q = q-p;
		}
	}
	int64 x=1;
	for(int i=path.size()-1;i>=0;--i)
	{
		if( path[i] == 1)
			x= (x<<1);
		else
			x=(x<<1) + 1;
	}
	cout<<x<<endl;
}
int main()
{
	int64 T,p,q,n,Num;
	cin>>T;
	while(T--)
	{
		cin>>n;
		if( n == 1)
		{
			cin >> Num;
			FindTheNode(Num);
		}
		else if( n == 2)
		{
			cin>>p>>q;
			FindTheNum(p,q);
		}
	}
	return 0;
}
温馨提示:上面说的取值是1-2^64需要用unsigned long long型!!!,我就是在这里wa了好多次.

你可能感兴趣的:(ACM)