#10018. 「一本通 1.3 例 1」数的划分 (深搜剪枝)

【题目描述】题目链接在此

将整数 nnn 分成 kkk 份,且每份不能为空,问有多少种不同的分法。当 n=7,k=3n=7, k=3n=7,k=3 时,下面三种分法被认为是相同的:1,1,51,1,51,1,5; 1,5,11,5,11,5,1; 5,1,15,1,15,1,1

【输入格式】

一行两个数 nnn , kkk。

【输出格式】

一行一个整数,即不同的分法数。

【样例输入】

7 3

【样例输出】

4

【样例解释】

四种分法为:1,1,51,1,51,1,5;1,2,41,2,41,2,4;1,3,31,3,31,3,3;2,2,32,2,32,2,3。

【数据范围与提示】

6≤n≤200,6 \leq n \leq 200,6≤n≤200, 2≤k≤62 \leq k \leq 62≤k≤6。

思路:

1.这道题就是组合排列的思路,但是不能重复,就是也是条件满足之后就进入,然后递归,接着找到之后就退出递归。

2.但是剪枝多了的就是要满足条件的思路,让时间减短,比如说:总和为7,然后要求分成3份,如果你找到的总和>7,那么这个可以直接删掉,就叫剪枝,反正简单来说就是让代码更简单清楚,更加好理解。

3.还有一个就是在剪枝递归这种类型的题目来说,一定要注意在主函数初始化,不然可能会出现bug。还有就是对于全排列和剪枝的区别就是,全排列可以有重复的,比如说:1 5 1,1 1 5这样的。但是剪枝这种题目是不能重复的,那么就要求找到上下界,简单来说就是要确定是升序排列还是降序排列,还是用剪枝的题目要诱惑你用全排列,这些都是要梳理清楚的。

4.最后一点就是,一定要剪枝剪枝剪枝,递归函数的int要搞清楚,不然一定会出错,还有一个就是占用完资源之后一定要释放资源,比如说: a[k]=i; dfs(k+1); a[k]=0; a[k]=0 表现出的就是释放资源。

然后我会发四个代码,没有理解组合排列递归的先理解组合排列递归,已经理解的就看组合改变,然后再看深搜剪枝,这样会很好理解,但是这些的时间都会比较长,所以我会用二维动态规划来实现,时间变短很多,但是要思考的更多。

代码实现组合排列:组合排列我没有写太多的注释,具体比较简单就是占用资源和释放资源,然后注释在这道题改变组合排列那里。caioj组合排列题目  
 

#include<cstdio>
#include<cstring>
int n,r,a[110],v[110];
void dfs(int k)
{
    if(k==r+1)
    {
        for(int i=1;i<=r;i++)printf("%d ",a[i]);
        printf("\n");
    }
    else
    {
        for(int i=a[k-1]+1;i<=n;i++)
            if(v[i]==1)
            {
                v[i]=0;
                a[k]=i;
                dfs(k+1);
                v[i]=1;
                a[k]=0;
            }
    }
}
int main()
{
    scanf("%d%d",&n,&r);
    for(int i=1;i<=n;i++) v[i]=1;
    a[0]=0;
    dfs(1);
    return 0;
}

代码实现改变自组合排列:730ms,最原始所以也最长时间

#include<cstdio>
#include<cstring>
int n,k,a[210],ans=0;//n表示号码有n个,分别是1~n,k表示从中提取k个,而且不能重复
//a数组表示一排格子,用来存号码,如: a[i]=7表示存了号码7,ans表示方案总数 
void dfs(int x,int sum)//x表示找的数,sum表示这些数的总和 
{
    if(x==k+1)//如果是第k+1个格子,说明前面已经处理好了 
    {
        if(sum==0) ans++;//表示这些数相加等于总和,成立,方案数+1 
    }
    else
    {
        for(int i=a[x-1];i<=sum;i++)//这个是升序排列,因为不能重复 
        {
            a[x]=i;//占用资源 
            dfs(x+1,sum-i);//表示下一个格子就是x+1,身上带着的资源是sum-i 
            a[x]=0;//释放资源 
        }
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    a[0]=1;//初始化 
    dfs(1,n);
    printf("%d\n",ans);
    return 0;
}

【代码实现深搜剪枝:430ms,已经因为剪枝优化的一些】

#include<cstdio>
#include<cstring>
using namespace std;
int n,m,a[10],ans;
void dfs(int k,int sum) //k表示找到的数,sum表示这些数的总和 
{
	if(sum>=n) return ; //剪枝,减少多余的东西 
	if(k==m) //如果要找的这个数等于总和 
	{
		if(n-sum>=a[k-1]) ans++; 
		//而且因为是剪枝升序所以 n-sum>=a[k-1],a[k-1]表示上一个格子,n-sum表示剩下的值 
	}
	else
	{
		for(int i=a[k-1];i<=n;i++) //升序的循环,如果从1开始就会出现重复,如1 5 1,1 1 5这样就重复 
			if(sum+i<n) //如果之前的总和+i这个数小于总数,说明可以进行下一步,
			//如果超过总数,就不行,因为要求的数肯定<=总数,多余就肯定错 
			{
				a[k]=i; //占用资源:第k个格子存进号码i 
				dfs(k+1,sum+i); //去下一个格子递归,当前的值是sum+i 
				a[k]=0; //释放资源:第k个格子踢走了存在自己里面的号码 
			}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	a[0]=1; 
	dfs(1,0); //初始化 
	printf("%d\n",ans);
	return 0;
}

【终极:动态规划:个人认为写的已经很详细了,上面的:10ms,超级快的】

#include<cstdio>
#include<cstring>
#include<algorithm>
int n,k;
int f[210][7];
int main()
{
	scanf("%d%d",&n,&k);
	memset(f,0,sizeof(f)); //f数组先初始化为0,
	f[0][0]=1;//但是第一个要为1,为了防止加来加去都是0 
	for(int i=1;i<=n;i++)//总和的循环 
		for(int j=i;j<=n;j++)//也是总和的循环,因为前面已经循环了i,所以j可以直接由i开始
		//循环两次是为了后面状态的处理做铺垫 
			for(int x=1;x<=k;x++)//这是循环方案数 
			{
				f[j][x]+=f[j-i][x-1];//f[j][x]就是方案总数的最大值
				//j-i是为了防止重复,比如说:1 1 5,1 5 1,5 1 1 这样的情况出现
				//x-1就是上一个的最大值,是为了继承的 
			}
	printf("%d\n",f[n][k]);//输出最大值 
	return 0;
}

这道题用我的话来说就是不能重复而且有条件的全排列,所以还是挺好理解的,就是理解的递归最主要的含义之后,一切就很好说明,难度系数大概为3。

猜你喜欢

转载自blog.csdn.net/qq_42367531/article/details/82049253
今日推荐