#10021. 「一本通 1.3 例 4」Addition Chains

版权声明:老江的博客,转载请告知老江 https://blog.csdn.net/qq_42367531/article/details/85231891

【题目描述】

原题来自:ZOJ 1937

已知一个数列a_0,a_1...a_ma 0 ​ ,a 1 ​ ...a m ​ (其中 a_0 = 1 , a_m = n , a_0 \lt a_1 \lt a_2 \lt ... \lt a_{m-1} \lt a_ma 0 ​ =1,a m ​ =n,a 0 ​ <a 1 ​ <a 2 ​ <...<a m−1 ​ <a m ​ )(其中 a_0=1 , a_m = n,a_{0}<a_{1}<a_{2}<...<a_{m-1}<a_{m})。对于每个 k,需要满足 a_{k}=a_{i}+a_{j}(0\leq i,j\leq k-1,这里的i可以与j相等)。现给定 n 的值,要求 m 的最小值(并不要求输出),及这个数列每一项的值(可能存在多个数列,只输出任一个满足条件的就可以了)。

【输入格式】

多组数据,每行给定一个正整数 n 。

输入以 0 结束。

【输出格式】

对于每组数据,输出满足条件的长度最小的数列。

【样例输入】

5
7
12
15
77
0

【样例输出】

1 2 4 5
1 2 4 6 7
1 2 4 8 12
1 2 4 5 10 15
1 2 4 8 9 17 34 68 77

【数据范围与提示】

1\leq n\leq 100,1\leq k\leq m

思路:

一直关注我博客的小可爱会发现,我的博客一向都是讲最优的,这个最优就包括了最好理解和最快的,所以同样,我今天会放上两个代码,一个是我自己打的最好理解的,另一个是后面研究出来的最快的。

老规矩,我们先从审题开始,题目中要我们求出一个m个数的序列,最大的am=n,也就是说(认真理解的人一定看出了),这道题的这么m个数的序列当中,必然会有1,n为什么呢?因为a1=1,am=n,所以必然有n,而且认真看样例的小可爱一定会知道,am个数的最后一个必然是n,而且必然能够凑出两个数相加等于他 

这种罗里吧嗦的东西我就不再讲了,直接回归题目。这道题要用搜索,因为我们要搜索ak=ai+aj,所以一定要深度搜索来实现。算法我们看出来了,但是这个递归的函数要怎样定义呢?

  1. 递归一个数,也就是说,dfs(int x)只有一个啊 ,那我们这个要怎么处理呢?(不知不觉切入到了我要讲的第一个好理解的代码)处理也不难,我们定义的这个x就为我们搜索到第几层,什么意思呢?简单来讲,我们已经不把这个题目中的序列看成一个序列,而是把他变成一棵树,往里面添加子节点。这就应了我的递归博客中的把递归当做一棵树来深入处理。那么知道了定义之后怎么处理是个大问题?傻子,定义了之后当然就是先剪枝啊啊啊啊啊,剪枝剪枝剪枝是什么鬼?这道题的剪枝就是如果这个搜索到的层数比我们定义的最小层数都要大的话就剪枝,还有一个就是如果这个搜索到层数的最大数比我们的n要大就剪枝。剩下的就是判断了,这里也可以不细讲,因为这道题不难。
  2. 递归两个数,一个是我们搜索到第几层,一个是当前这一层的值,这个快的原因就是他不用顺其自然的输出值,而是直接把值给你有了一个完整的规划,所以才会让代码更快。剪枝原理也是一样的啦。

【代码实现一:最好理解:200多ms】

/*稍微说一下:就是题目中给出的n是我们要求的那一串数列当中的最大值*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,a[2100],minn=99999,ans[2100];
//a数组表示我们在递归当中记录的值
//minn表示我们搜索到的最小层数
//ans表示我们输出的最后答案 
void dfs(int x)//x表示我们搜索到第几层 
{
	if(x-1>minn)return;/*如果我们搜索到的这一层的前一层,
	已经比我们定义的最大层数要大的话,就可以返回上一层递归了*/
	if(a[x-1]>n)return;/*如果我们记录的a数组的当前这一层的前一层的值,
	就已经比n要大的话,就返回到上一层递归*/ 
	if(a[x-1]==n)/*如果我们当前的这一层的前一层的值,刚好等于n,说明这可以作为一个完整的数列*/
	{
		if((x-1)>=minn)return;//如果我们找到的层数的前一层大于我们搜索到的最小层数,返回 
		minn=x-1;//否则更新最小层数 
		for(int i=1;i<x;i++)ans[i]=a[i];//并且把当前状态的a数组更新到ans数组当中,作为一种答案标准 
	}
	else//否则 
	{
		for(int j=x-1;j>=1;j--)/*因为我们记录的x的是从前往后的,
		所以我们的这个j一定是从后往前来匹配,题目中要求的这个ak=ai+aj,只要有一个不符合就是不符合的了*/ 
		{
			if(a[x-1]+a[j]<=n)//要确保他们相加的值是小于等于最大值n的 
			{
				a[x]=a[j]+a[x-1];/*这个就是满足题目的要求,当前的这个值一定要是上一层的值+我们枚举的值*/ 
				dfs(x+1);//调用自己 
				a[x]=0;//返回完答案回到这里的时候,要清零,因为我们还有其他的j要枚举 
			}
		}
	}
}
int main()
{
	while(scanf("%d",&n)!=EOF)//多组数据 
	{
		if(n==0)break;//输到0的时候就结束输入 
		a[1]=1;//初始化第一层是1 
		minn=999999;//我们的定义的最小层数,在后面会不断更新 
		dfs(2);//从第二层开始递归 
		for(int i=1;i<=minn;i++)printf("%d ",ans[i]);//求出了最小层数之后,就输出就好了 
		printf("\n");//多组数据输出 
	}
	return 0;
}

【代码实现二:最快:10ms内】

#include<cstdio>
#include<cstring> 
#include<iostream>
using namespace std;
int n;
bool flag;//表示我们的目标 
int ans[10050];//记录输出的答案 
int minn;//表示我们所找到的最小层数 
inline void dfs(int dep,int pos)//dep为层数,pos为值 
{
    if(dep==minn+1)//如果超过了 
    {
        if(ans[minn]==n) flag=true;//并且最后一层的值等于最大的n,就意味着找到目标 
        return;//返回 
    }
    for(int i=dep-1;i>=pos;i--)//继续满足题目中的ak=ai+aj 
    {
        for(int j=i;j>=1;j--) 
        {
           int f=ans[dep]=ans[i]+ans[j];//我们当前搜索的值要满足:它等于两个数相加 
           for(int k=pos+1;k<=minn;k++) f<<=1;//等于:f*2 
           if(f<n)break;//n是最大的,所以肯定可以组合起来 
           dfs(dep+1,i+1);//递归下一层的值为i+1 
           if(flag)return;//如果找到目标就返回 
           ans[dep]=0;//同样清零,为下一次准备 
        }
    }
}
int main()
{
    while(~scanf("%d",&n))//多组数据输入 
    {
    	if(n==0)break;
        if(n==1)//判断特殊情况节省时间 
        {
            printf("1\n");
            continue;//跳出单次while,继续下一次的输入 
        }
        flag=false;//初始化都不可以 
        ans[1]=1;//初始化第一层 
        for(int i=2;i<=n;i++)//从第二层开始 
        {
            minn=i;//假设我们的minn,也就是所需的层数就是我们的递归层数 
            dfs(2,1);//递归第二层,值为1 
            if(flag)
            {
                for(int i=1;i<=minn;i++) printf("%d ",ans[i]);//输出答案 
				printf("\n");
        		break;
            }
        }
    }
    return 0;
}

好了,到此结束,希望持续关注哦! 

 

猜你喜欢

转载自blog.csdn.net/qq_42367531/article/details/85231891