12行代码AC——例题6-6 小球下落(Droppint Balls, UVa 679)——解题报告

励志用尽量少的代码做高效的表达。


提交(题目)链接→UVa-679


题目大意:

有一颗满二叉树,每个节点是一个开关,初始全是关闭的,小球从顶点落下,如果开关打开,则落向该节点的右子树,如果开关关闭,则落向该节点的左子树。小球每次经过开关就会把它的状态置反,问第k个球下落到d层时经过的开关编号。

思路分析;

最初的想法是:使用一维数组模拟树的遍历。

因为题目比较简单,就没有估算时间复杂度。 写出来后提交,显示TLE。
于是结合紫书自己分析了一下:
因为I的取值可以最大为2^21-1,而最大深度为20,因此一组数据的计算量就高达1000W。而一共可能有10000组数据。 因此常规方法一定会超时
仔细看了解析,敲出了优化方法。

优化的想法是:规律推导+数学公式实现。

因为每一个小球都会落到根节点上,因此前两个小球必然是一个在左子树的最左侧,一个在右子树的最左侧。
因此可得:只需看小球的奇偶性,就能知道他是最终在哪棵子树。
同理:对于那些落入根节点左子树的小球来说,只需知道该小球是第几个落在根的左子树里的,就可以知道下一步是往左还是往右了。
如果使用题目中给出的编号I,则当I是奇数时,它是往左走的第(I+1)/2个小球,当I是偶数时,它是往右走的第I/2个小球。 可以直接模拟最后一个小球的路线:
这样程序的运算量就与小球编号无关了,而且节省了一个巨大的数组。
(PS:敲到这里,偶然想起从前做过的一道题:大致是求1-n的连加和。如果直接遍历,值会溢出。 最后采用高斯公式:sum = n(n-1)/2解决问题。 规律果然都是相通的。)*


下面把两种代码都贴出来,供学习参考。

超时的代码:

#include<stdio.h>
#include<cstring>
const int maxd = 20;
int s[1<<maxd];
int main() {
	int n; scanf("%d", &n); while(n--) {
		int D, I; scanf("%d %d", &D, &I); {
			memset(s, 0, sizeof(s));							//a数组从1开始运算 
			int k, n1 = (1<<D)-1;
			for(int i = 0; i < I; i++) {						//遍历小球 
				k = 1;
				for(;;) {
					s[k] = !s[k];
					k = s[k] ? k*2 : k*2+1;
					if(k > n1) break;
				}
			}
			printf("%d\n", k/2);
		}
	}
	return 0;
}

AC代码:

#include<stdio.h>
int main() {
	int n; scanf("%d", &n); while(n--) {
		int D, I; scanf("%d %d", &D, &I);
		int k = 1;
		for(int i = 0; i < D-1; i++) 
			if(I%2) { k *= 2; I = (I+1)/2; }
			else { k = (k*2+1); I /= 2; }
		printf("%d\n", k);
	} 
	return 0;
} 

收获:

1、对树遍历的模拟
2、规律推导+数学公式的实现。


择苦而安,择做而乐;虚拟现实终究比不过真实精彩之万一。

发布了97 篇原创文章 · 获赞 104 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43899069/article/details/104858060