问题:
将正整数n表示成一系列的正整数之和,n=n1+n2+…+nk(n1>=n2>=…>=nk>=1,k>=1).正整数n的这种表示称为正整数n的划分。
1.递归
分析:
假设能划分的最大整数为m,用q(n,m)来表示其能划分的种类数,则分为两种情况:n<=m、n>m(n<m和n=m合并为一种)。
当n<=m时,q(n,m)=q(n,n),计算q(n,n)可将其存在最大划分数m和不存在m的情况,存在最大划分数m的情况只有一种,不存在最大划分数的有q(n,n-1)。两者相加即可。
当n>m时,存在最大划分数的时候,还有n-m个数可划分,最大划分数仍然存在m,则存在q(n-m,m)种;当不存在最大划分数的时候,存在q(n,m-1)。两者相加即可。
综上所述:
代码:
#include<stdio.h>
int q(int n,int m){
if(n<1||m<1)
return 0;
if(n<=m)
return 1+q(n,n-1);
if(n>m)
return q(n-m,m)+q(n,m-1);
}
int main(){
int n,m;
printf("请输入要划分的正整数和最大划分数:\n");
scanf("%d%d",&n,&m); //输入要划分的数字和最大划分数
printf("%d",q(n,m));
return 0;
}
2. 动态规划
分析:
分析与递归一致,需要注意初始条件。
在这里,由一个a[N][N]的二维数组来存放每一个数的划分种类数,边界条件应该是a[i][1]和a[1][i]均为1。a[n][m]即所要求的种类数。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=500;
int a[N][N];
int main(void){
int n,m;
cout<<"请输入要划分的数和其能划分的最大数字:"<<endl;
cin>>n>>m;
//边界条件
for(int i=1;i<=n;i++)
a[i][1]=1;
for(int i=1;i<=m;i++){
a[1][i]=1;
}
for(int i=2;i<=n;i++)
for(int j=2;j<=m;j++){
if(i>j)
a[i][j]=a[i-j][j]+a[i][j-1];
else
a[i][j]=a[i][i-1]+1;
}
cout<<"有"<<a[n][m]<<"种情况";
return 0;
}
3.回溯(dfs+剪枝)--》仅考虑了n=m的情况
dfs基本思想:每一次都能划分从1-n,如果其值加起来等于n,就将其拿出来。(时间和空间复杂度都太大了,需要适当剪枝处理)
1)将所划分值从大到小排序
分析:
需要三个参数m,i,h来表示要划分的数,位置,最大可划分的数。可划分的数j从h-1开始列举,获得一个划分数之后,需要划分的数m=m-j;为了使得列举不重复,最大可划分的数h必须要小于等于上一个数字,但是又不能超过紧接着还需要划分的数,所以h=min(m-j,j)。跳出循环的条件即是m==0时。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int x[N]; //解向量
int m,cnt=0;
//m划分的数,i位置 ,h最大划分数
//从大到小排列
void output(int i){
printf("%-2d:",cnt);
cout<<m<<"=";
for(int k=1;k<i-1;k++)
cout<<x[k]<<"+";
cout<<x[i-1]<<endl;
}
void huafen(int m,int i,int h){
if(m==0){
cnt++;
output(i);
}else{
for(int j=h;j>=1;j--){
x[i]=j;
huafen(m-j,i+1,min(m-j,j));
}
}
}
int main(void){
cin>>m;
huafen(m,1,m);
return 0;
}
结果示例:
2)将所划分值从小到大排序
分析:
与划分值从大到小差不多。需要三个参数m,i,h来表示要划分的数,位置,最小可划分的数。可划分的数j从h-m开始列举,获得一个划分数之后,m=m-j,h为了使得列举不重复,必须要大于等于上一个数字,所以h=j。m跳出循环的条件即是m==0时。
值得注意的是,能够接着递归下去的条件是m-j>=j或者m-j==0。m-j>=j是因为m-j为还需要划分的数,j为最小的划分值,不可能(还需要划分的数)<(最小的划分值),这样子递归无法进行。m-j==0可以接着递归的原因是使得下一次能进入m==0,然后正确输出。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=500;
int x[N],m; //x为解向量
int cnt=0;
void output(int i){
printf("%-2d:",cnt);
cout<<m<<"=";
for(int k=1;k<i-1;k++)
cout<<x[k]<<"+";
cout<<x[i-1]<<endl;
}
//m划分的数,i位置,h最小划分数
void huafen(int m,int i,int h){
if(m==0){
cnt++;
output(i);
}else{
for(int j=h;j<=m;j++){
x[i]=j;
if(m-j>=j||m-j==0){
huafen(m-j,i+1,j);
}
}
}
}
int main(void){
cin>>m;
huafen(m,1,1);
return 0;
}
结果示例:
疑问?
为什么在这里不用恢复m、h的状态?
答:在这里,我们在需要回溯的地方(huafen()函数)对其参数进行修改,相当于形参,在返回时对外面的数据不会产生影响(除非用了指针,这里没用指针哈哈哈)。
over,终于搞懂了,撒花!!!
自己写的,有啥问题,欢迎指正!嘿嘿。