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

来源:互联网 发布:photoshop cc软件下载 编辑:程序博客网 时间:2024/05/21 17:58
题目1543:无限完全二叉树的层次遍历

时间限制:1 秒

内存限制:128 兆

特殊判题:

提交:340

解决:46

题目描述:

有一棵无限完全二叉树,他的根节点是1/1,且任意一个节点p/q的左儿子节点和右儿子节点分别是,p/(p+q)和(p+q)/q。如下图:

它的层次遍历结果如下:
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]范围内。

样例输入:
41 22 1 21 52 3 2
样例输出:
1 223 25

思路:第一眼看到这个题目"按层遍历",联想起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 nodepath.push_back(1);  //1 means leftelse path.push_back(0); //0 means rightx=y;}int64 p=1,q=1;for(int i=path.size()-1;i>=0;--i){if( path[i] == 1)q=p+q;elsep=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);elsex=(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了好多次.
原创粉丝点击