总时间限制: 200ms 内存限制: 65536kB
描述
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。
输入
标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。
(0 < N <= 50, 0 < K <= N)
输出
对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目
样例输入
5 2
样例输出
2
3
3
提示
第一行: 4+1, 3+2,
第二行: 5,4+1,3+2
第三行: 5,1+1+3, 1+1+1+1+1+1
#include
#include
using namespace std;
//a[i][j] 和为i, j个正整数
//
//第一行: N划分成K个正整数之和的划分数目 a[i][j] j=k
//第二行: N划分成若干个不同正整数之和的划分数目 b[i][j] i=n不同正整数
//第三行: N划分成若干个奇正整数之和的划分数目 c[i][j] i=n 奇正整数
//a
//初始状态: a[i][1]=1
//分类——包含1和不包含1
//a[i][j]=a[i-1][j-1]
// +1就为当前值——故必包含1
// if i-j>j ---- a[i][j]+=a[i-j][j] 每个整数-1,则原来为1的就为0了
int a[60][60]={0};
void q1(int N,int k){ //a[N][K] 为和为i,
for (int i=0;i<=N;i++) {
a[i][1]=1;
}
for (int i=1;i<=N;i++){
for (int j=2;j<=k;j++){
a[i][j]=a[i-1][j-1];
if (i-j>=j){
a[i][j]+=a[i-j][j]; //每个数都-1,则剩下的方案里不包含j
}
}
}
}
//N划分成若干个不同正整数之和的划分数目
//最大值为T=N的不同正整数之和
//b[i][j]= 不包括N和包括N 从前T(1——T)种数字钟选择一些——凑成和i的做法数目
// 如果i=0; return1;
// 如果t=0; return0;
// return [i][t-1]+[i-t][t-1]
int b[60][60]={0};
void q2(int N,int t){
b[0][0]=1; //用0来划分0 //每个数用一次
for (int i=0;i<=N;i++){
for(int t=1;t<=N;t++){
b[i][t]=b[i][t-1]; //不用最大的数
if (i-t>=0)
b[i][t]+=b[i-t][t-1]; // 用最大的数
}
}
}
// n划分成若干个奇正整数之和
//与整数拆法类似——考虑有没有1
//没有1—— -2*j
//因为没有1的话最小的就为3以上
//c[i][j]=c[i-2*j][j]+c[i-1][j-1]
// 没有1的 所有有1的
// 每个都减去2 ——减一的话就都剩偶数了——-2就为计数
int c[60][60]={0};
void q3(int N, int t){ //i划分成j个奇正整数之和的划分数目
for (int i=1;i<=N;i+=2) {
c[i][1]=1; //奇数的&1为1
}
for (int i=2;i<=N;i++){
for (int j=2;j<=i;j++){
c[i][j]=c[i-1][j-1];
if (i-j>=j){
c[i][j]+=c[i-2*j][j];
}
}
}
}
int N;
int k;
int main() {while(cin>>N>>k){
q1(N,k);
cout<<a[N][k]<<endl;
q2(N,N);
cout<<b[N][N]<<endl;
q3(N,N);
int sum=0;
for (int i=1;i<=N;i++) sum+=c[N][i];
cout<<sum<<endl;
}}
解题思路
使用动态规划需要满足的条件:
子问题的最优解可以用于递推母问题的最优解;
问题一
将N划分成K个正整数之和,求可划分的数目。
a[i][j]表示j个正整数求和为i;
目标为a[N][K]
初始状态:a[i][1]=1
难点:
分类方法: 表达式中包含1和不包含1
包含1的表达式——a[i][j]=a[i-1][j-1]
,即前一个数所有的表达式+1即可;
不包含1的表达式——a[i-j][j]
,当作最小为2,则每个数-1后最小为1。
int a[60][60]={0};
void q1(int N,int k){ //a[N][K] 为和为i,
for (int i=0;i<=N;i++) { //初始状态,一个正整数凑N,表达式只有一种
a[i][1]=1;
}
for (int i=1;i<=N;i++){
for (int j=2;j<=k;j++){ //2个以上正整数和为i
a[i][j]=a[i-1][j-1]; //包含1的数
if (i-j>=j){ //i<2*j的话肯定包含1,不可能全大于等于2
a[i][j]+=a[i-j][j]; //每个数都-1,则剩下的方案里不包含1
}
}
}
}
问题2
N划分成若干个不同正整数之和的划分方法总数;
b[i][j], i划分成小于等于j的不同正整数之和的划分方法数;
目标为b[N][N]
初始状态:b[0][0]=1; ——0的和为0
难点:
递推方法 ——当前b[i][j]的方法数等于包含J和不包含J的方法数相加
递推公式
不包含j的方法数——b[i][j-1]
包含j的方法数——b[i-j][j-1] (i>=j时),i
递推方向——(先从左往右推)——(再从上往下推)–初始状态确定0,0即可
int b[60][60]={0};
void q2(int N,int t){
b[0][0]=1; //用0来划分0 //每个数用一次
for (int i=0;i<=N;i++){
for(int t=1;t<=N;t++){
b[i][t]=b[i][t-1]; //不用最大的数
if (i-t>=0)
b[i][t]+=b[i-t][t-1]; // 用最大的数
//满足每个数只用一次
}
}
}
问题3
n划分成若干个奇正整数之和,求划分方法数
c[i][j]——用j个数求和i
总划分数为i行所有j的和。
初始状态——c[i,i为奇数][1]=1,因为一个数只有一种表达式
分类方法
包含1——c[i-1][j-1]
不包含1——c[i-2j][j], (i>3j),因为如果i<3*j,表达式中必含1
目标为C[N][1]加到C[N][N]的和;
int c[60][60]={0};
void q3(int N, int t){ //i划分成j个奇正整数之和的划分数目
for (int i=1;i<=N;i+=2) {
c[i][1]=1; //奇数可以划分为1个奇正整数,偶数不行
}
for (int i=2;i<=N;i++){
for (int j=2;j<=i;j++){
c[i][j]=c[i-1][j-1];
if (i-j>=j){
c[i][j]+=c[i-2*j][j];
}
}
}
}
与问题一:N划分成k个整数之和的划分方法数做法相同。
k从1取到N的和就为总的方法数;
求j个数中不包含1的和为i的表达式
此表达式全部大于等于3,则每个数-2,最小值为1,也有不包含1的。
[i-2j][j]的总数为[i][j]中不包含1的数量。