动态规划 001:复杂的整数划分问题

001:复杂的整数划分问题

总时间限制: 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 (每个方法先减去j,剩余的数用比j小的数求和的方法数即为包含j的方法数)
递推方向——(先从左往右推)——(再从上往下推)–初始状态确定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的数量。

你可能感兴趣的:(算法,动态规划)