PAT (Basic Level) Practice 1005 继续(3n+1)猜想

探讨卡拉兹猜想验证过程中的关键数识别算法,通过分析递推数序列,提出高效计算关键数字的方法,包括动态规划、剪枝技巧及排序输出策略。

题目

卡拉兹(Callatz)猜想已经在1001中给出了描述。在这个题目里,情况稍微有些复杂。

当我们验证卡拉兹猜想的时候,为了避免重复计算,可以记录下递推过程中遇到的每一个数。例如对 n = 3 n=3 n=3 进行验证的时候,我们需要计算 3 、 5 、 8 、 4 、 2 、 1 3、5、8、4、2、1 358421,则当我们对 n = 5 、 8 、 4 、 2 n=5、8、4、2 n=5842 进行验证的时候,就可以直接判定卡拉兹猜想的真伪,而不需要重复计算,因为这 4 个数已经在验证3的时候遇到过了,我们称 5 、 8 、 4 、 2 5、8、4、2 5842是被 3 3 3“覆盖”的数。我们称一个数列中的某个数 n 为“关键数”,如果 n 不能被数列中的其他数字所覆盖。

现在给定一系列待验证的数字,我们只需要验证其中的几个关键数,就可以不必再重复验证余下的数字。你的任务就是找出这些关键数字,并按从大到小的顺序输出它们。

输入格式:

每个测试输入包含 1 个测试用例,第 1 行给出一个正整数 K ( < 100 ) K (<100) K(<100),第 2 行给出 K K K 个互不相同的待验证的正整数 n ( 1 < n ≤ 100 ) n (1<n≤100) n(1<n100)的值,数字间用空格隔开。

输出格式:

每个测试用例的输出占一行,按从大到小的顺序输出关键数字。数字间用 1 个空格隔开,但一行中最后一个数字后没有空格。

输入样例:

6
3 5 6 7 8 11

输出样例:

7 6

思路&总结

应该有更好的方法存储输入数据,提高计算关键数字的效率,比如,动态规划,剪枝,我先去做饭,等我回来再搞搞。(2020-4-8 11:25:36)
真的是,中午菜搞的差不多了才想起,忘了煮饭。服气。
思路

例如对 n = 3 n=3 n=3 进行验证的时候,我们需要计算 3 、 5 、 8 、 4 、 2 、 1 3、5、8、4、2、1 358421,则 3 、 5 、 8 、 4 、 2 、 1 3、5、8、4、2、1 358421称为递推数
1、把输入数据放在numbers里面。count存储递推数出现次数,类似键值对,最终count[5]=2表示5这个数字在整个程序运行过程中的在递推数中出现的次数是2。
2、迭代砍半过程,将递推数出现次数记录在count[i]中,比如递推3时,出现一个5,则count[5]++,然后出现一个8,则count[8]++,依次类推。
3、最后i从大到小判断count[i]中的值,如果不是1,则说明被覆盖过,不是关键数。
*由于不用计算覆盖次数,所以count[i]只要大于2,则说明被覆盖过,剩下的递推数也已经被覆盖过,可以直接跳出,提高效率。

上面答案1的问题主要是,忘了 3 n + 1 3n+1 3n+1过程可能会超过给定数 n n n 的范围( n < 100 n<100 n<100),使用下标记录被重复次数应该把大于100的给跳过记录,反正最后也不用输出。时间倒是没什么大问题,稍微做了一下剪枝,叫剪枝可能不太合适,因为是循环不是递归,将就一下吧。(我不会忘了这个题,它让我忘了煮饭!)(2020-4-8 13:42:36)
等下有空,百度一下dalao的做法。

答案(3正确2段错误 2020-4-8 11:22:01)

#include <stdio.h>

int count[100];
int numbers[100];//input integers

void toCover()
{
	int i;
	//iterate numbers , if the path of 3n+1 to 1 cover m,
	//then count[m]++
	for(i = 0; i < 100; i++ )
	{
		count[i]++;// for output convienently
		int t = numbers[i];
		if(t != 0)
			while(t != 1)
			{
				if(t%2 == 0 )
				{
					t = t/2;
					count[t]++;
				}else{
					t = 3 * t +1;
				}
			}
	}
}

int isNumbersHasK(int k)
{
	int i = 0;
	for(i = 0; i < 100; i++ )
	{
		if(numbers[i] == k)
			return 1;
	}
	return 0;
}
int main(int argc, char* argv[])
{
	int i = 0;
	for(i = 0; i < 100; i++ )
	{
		count[i] = 0;
		numbers[i] = 0;
	}
	int n;
	scanf("%d",&n);
	for(i = 0; i < n; i++ )
		scanf("%d",&numbers[i]);

	toCover();
	//the key num m is the count[m]=1
	
	for(i = 100; i >= 1; i-- )
	{
		if(count[i] == 1 && isNumbersHasK(i) == 1)
		{
			printf("%d",i);
			break;
		}
	}
	--i;
	for(; i >= 1; i-- )
	{
		if(count[i] == 1 && isNumbersHasK(i) == 1)
			printf(" %d",i);
	}
	return 0;
}

答案2(2020-4-8 13:34:58)

#include <stdio.h>
int count[100];
int numbers[100];//input integers

void toCover(int n)// n is numbers length
{
	int i;
	//iterate numbers , if the path of 3n+1 to 1 cover m,then count[m]++
	for(i = 0; i < n; i++ )
	{
		int t = numbers[i];
		count[t]++;// for output convienently
		if(t != 0)
			while(t != 1)
			{
				if(t%2 == 0 )
				{
					t = t/2;
					//version2.0 revise here
					if(t <= 100)
					{
						if(count[t] > 1)
							break;
						count[t]++;
					}
					//revise done
				}else{
					t = 3 * t +1;
				}
			}
	}
}

int isNumbersHasK(int k)
{
	int i = 0;
	for(i = 0; i < 100; i++ )
	{
		if(numbers[i] == k)
			return 1;
	}
	return 0;
}
int main(int argc, char* argv[])
{
	int i = 0;
	for(i = 0; i < 100; i++ )
	{
		count[i] = 0;
		numbers[i] = 0;
	}
	int n;
	scanf("%d",&n);
	for(i = 0; i < n; i++ )
		scanf("%d",&numbers[i]);

	toCover(n);
	//the key num m is the count[m]=1
	for(i = 100; i >= 1; i-- )
	{
		if(count[i] == 1 && isNumbersHasK(i) == 1)
		{
			printf("%d",i);
			break;
		}
	}
	--i;
	for(; i >= 1; i-- )
	{
		if(count[i] == 1 && isNumbersHasK(i) == 1)
			printf(" %d",i);
	}

	return 0;
}

思路2

百度dalao niubility。每次觉得自己做出来了题都很有成就感,一查百度总会发现自己是个弟中弟。
思路

例如对 n = 3 n=3 n=3 进行验证的时候,我们需要计算 3 、 5 、 8 、 4 、 2 、 1 3、5、8、4、2、1 358421,则 3 、 5 、 8 、 4 、 2 、 1 3、5、8、4、2、1 358421称为递推数
1、把输入数据放在numbers里面,称为输入数
2、迭代砍半过程,将所有在递推数中出现的输入数改成1。比如按输入样例输入3 5 6 7 8 11这些输入数,递推3时,出现递推数 3 、 5 、 8 、 4 、 2 、 1 3、5、8、4、2、1 358421,则把输入数更新为3 1 6 7 1 11
3、排序一次,从大到小输出不是1输入数
*再说一遍niubility。由于58在递推3时已经被覆盖,可以把58看成3的子集,只要完整递推一遍3,则不需要再递推5858也不可能是关键数,直接用1覆盖。最后剩下的非1输入数就是没被覆盖输入数,即关键数

总结

sort(int a[],int a+n) 来自c++的 #include <algorithm> using namespace std;
c语言有个qsort()函数好像要自己写comp,下次再,学。

答案3(2020-4-8 14:58:23)

#include <stdio.h>
#include <algorithm>
using namespace std;
int numbers[100];//input integers

int main(int argc, char* argv[])
{
	int i = 0;
	int j = 0;
	int x = 0;
	for(i = 0; i < 100; i++ )
		numbers[i] = 0;
	int n;
	scanf("%d",&n);
	for(i = 0; i < n; i++ )
		scanf("%d",&numbers[i]);
	for(i = 0; i < n; i++ )
	{	
		x = numbers[i];
		while(x != 1)
		{
			if(x%2==0)
				x /= 2;
			else
				x = (3 * x + 1) / 2;
			for(j = 0; j < n ; j++)
			{
				if(numbers[j] == x)
				{
					numbers[j] = 1;
					break;
				}
			}
		}
	}
	
	sort(numbers, numbers + n);

	for(i = n - 1; i >= 0; i-- )
	{
		//I don't know why here won't come out with 
		//IndexOutOfBoundsException errors or something 
		//it just print numbers[-1] = 0. !!!
		//baidu say c program language won't test index 
		//unless the memory you visit isn't belong to this program
		//printf("%d %d %d \n",i,numbers[i],numbers[i-1]);
		if(numbers[i] != 1)
			printf("%d",numbers[i]);
		if(numbers[i-1] > 1)//may wrong,but i would'n reivse it
				printf(" ");
	}

	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值