原题:
http://acm.fzu.edu.cn/problem.php?pid=1207
解题分析:
要注意的有三点:
1) “从 N 开始依次产生半数集 ……”. 要注意 ” 依次 ” 的表述 , 说明半数集中元素的产生是有先后顺序的 . 这对后面的排除重复元素很重要 .
2) “在 N 的左边加上一个自然数 , 但该自然数不能超过最近添加数的一半 .” 这是构造解集的重要条件 , 引导我们使用递归方法解决此题 ..
3) “半数集不是多重集 . 集合中已经有的元素不再添加到集合中 “. 配合 1), 就可以了解产生重复的原因 .
重复分析: 假设 N=50. 那么会出现如下情况 :
50 -> 12 50
50 -> 2 50 -> 1 2 50 (与之前产生的 1250 重复 , 重复一次 )
其实, 重复造成的问题不止如此 , 考虑下面的情况 :
50 -> 48 50
50 -> 8 50 -> 4 8 50
4 8 50与已经产生的 48 50 重复 . 这不仅造成自身不能添加到半数集中 , 而且使得由 4 8 50 扩展形成的 2 4 8 50 和 1 2 4 8 50 都不会出现在半数集中 .
所以, 扣除重复元素的操作要注意两个问题 :
1) 产生重复的条件.
2) 应该扣除多少个元素.
产生重复的条件: 根据题意 , 0<N<201, 所以 , 0<N/2<=100. 第二次添加的数肯定是个两位数 . 也就是说 , 重复是由于一个两位数和两个一位数的冲突造成的 . 由添加数的规则可得 , 当添加的数为 i 时 , 若 i > 9 && ( (i / 10) <= (1 % 10) / 2).
应该扣除的个数: 由构造的顺序性可知 , 与一个两位数构造成的半数集元素重复的元素不能添加到半数集中 . 要扣除的个数就是由最左边的一位数能够产生的半数集数量 . 例如 ,
50 -> 48 50
50 -> 8 50 -> 4 8 50
要扣除的数量就是4 能够产生的半数集元素个数。
源码:(VC++)
#include <stdio.h> int total; //数组中的值 , 表示与下标等值的数所能产生的半数集的元素个数 int map[] = {1, 1, 2, 2, 4, 4, 6, 6, 10, 10}; void nextSet(int n); int main() { //freopen(“input.txt”, “r”, stdin); int n; while(scanf(“%d”, &n) != EOF) { total = 1; nextSet(n); printf(“%d/n”, total); } return 0; } void nextSet(int n) { if(n > 1) { int t = n / 2; for(int i = 1; i <= t; i++) { nextSet(i); total++; if(i > 9 && ( (i / 10) <= (i % 10) / 2)) { total -= map[(i / 10)]; //扣除重复 } } } }