zoukankan      html  css  js  c++  java
  • 整数划分

    整数划分问题

    给两个整数 (n)(k)


    将 n 划分成 k 个正整数之和的划分数

    n=m1+m2+...+mi; (其中mi为正整数,并且1 <= mi <= n),则{m1,m2,...,mi}为n的一个划分。

    如果{m1,m2,...,mi}中的最大值不超过m,即max(m1,m2,...,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);

    例如当n=4时,他有5个划分,{4},{3,1},{2,2},{2,1,1},{1,1,1,1};

    注意4=1+3 和 4=3+1被认为是同一个划分。

    该问题是求出n的所有划分个数,即(f(n, n))。下面我们先考虑求(f(n,m))的方法;

    递归

    (1)当(n=1)时,不论(m)的值为多少((m > 0) ),只有一种划分即 { 1 };

    ​ (2) 当 (m = 1) 时,不论(n)的值为多少,只有一种划分即 (n) 个 1,{ 1, 1, 1, ..., 1 };

    (3) 当 (n = m) 时,根据划分中是否包含 (n),可以分为两种情况:

    ​ (a). 划分中包含(n)的情况,只有一个即 { n };

    ​ (b). 划分中不包含(n)的情况,这时划分中最大的数字也一定比 (n) 小,即 (n) 的所有 ( (n - 1) ) 划分。

    ​ 因此 (f(n, n) = 1 + f(n, n-1));

    ​ (4) 当 (n < m) 时,由于划分中不可能出现负数,因此就相当于(f(n, n));

    ​ (5) 但 (n > m) 时,根据划分中是否包含最大值 (m),可以分为两种情况:

    ​ (a). 划分中包含 (m)的情况,即 { m, { x1, x2, ..., xi } }, 其中 { x1, x2, ..., xi } 的和为 (n - m),可能再次出现 (m),因此是((n - m))的 (m) 划分,因此这种划分

    ​ 个数为 (f(n-m, m));

    ​ (b). 划分中不包含 (m) 的情况,则划分中所有值都比 $m $小,即 (n) 的 ((m - 1) ) 划分,个数为 (f(n, m - 1));

    ​ 因此 (f(n, m) = f(n - m, m) + f(n, m - 1));

    综合以上情况,我们可以看出,上面的结论具有递归定义特征,其中(1)和(2)属于递归边界,(3)和(4)属于特殊情况,将会转换为情况(5)。而情况(5)为通用情况,其本质主要是通过减小(m)以达到递归边界,从而解决问题。其递推表达式如下:

    [if(n==1||m==1) return 1;\ if(n<m) return f(n,n);\ if(n==m) return 1+f(n,m-1);\ if(n>m) return f(n-m,m)+f(n,m-1);\ ]

    for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++) {
    		if(i==j) dp[i][j]=dp[i][j-1]+1;
    		else if(i<j) dp[i][j]=dp[i][i];
    		else dp[i][j]=dp[i][j-1]+dp[i-j][j];
    	}
    		 
    

    将 n 划分成最大数不超过 k 的划分数

    跟1的情况是一样的,最后输出(dp[n][k])即可


    将 n 划分成若干奇正整数之和的划分数

    这个呢,我们首先需要调整边界状态:当(m=1)时,(f(n,m)=1);当(n=1)(m>1)时,(f(n,m)=0)

    其次,我们需要调整状态转换公式:

    (f(n-m,m)+f(n,m-1); (n>m)) 应该更改为:(f(n-m,m)+f(n,m-2); (n>m))

    (n=m)时,考虑是否取(m)值,分为两种情况:

    (m)值,那么就只有一个(n)

    不取(m)值,那么转化为(f(n,m-2))

    所以$f(n,m)=1+f(n,m-2) $

    memset(dp,0,sizeof(dp));
    	for(int i=0;i<=n;i++){
    		dp[i][1]=1;
    		if(i&1)//判断i是否是奇数,这是一个位运算 
    		dp[0][i]=1;
    	}
    	for(int i=1;i<=n;i++){
    		for(int j=1;j<=n;j++){
    			if(j&1){
    				if(j<=i){
    					dp[i][j]=dp[i-j][j]+dp[i][j-1];
    				}
    				else{
    					dp[i][j]=dp[i][i];
    				}
    			}
    			else{
    				dp[i][j]=dp[i][j-1];
    			}
    		}
    	} //
    

    将 n 划分成若干不同整数之和的划分数

    只需要考虑当(n>m)时,考虑是否取(m)

    (m)值,即{m, {x1,x2,...xi}}, 其中{x1,x2,... xi} 的和为(n-m),可能再次出现(m),因此是((n-m))的(m)划分,因此这种划分个数为(f(n-m, m-1)); 因为m不能再取了

    不取(m)值, 划分中不包含(m)的情况,则划分中所有值都比(m)小,即(n)的((m-1))划分,个数为(f(n,m-1));

    所以(n>m)的情况,(f(n,m)=f(n-m,m)+f(n,m-1))

    终止条件 :

    (n=1)(m>=1) 肯定是1

    但是当(n=1 ,m<1)或者 (n>1 m=1)时显然是0

    for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++) {
    		if(i==j) dp[i][j]=dp[i][j-1]+1;
    		else if(i<j) dp[i][j]=dp[i][i];
    		else dp[i][j]=dp[i][j-1]+dp[i-j][j-1];
    	}
    		 
    

    将n划分为k个数的划分数(一个划分中正好有k个数)

    划分中是否包含1,包含的话就是(dp[i-1][k-1])(把1拿出来,然后将剩下的分为k-1份),不包含的话就是(dp[i-k][k])(把1分给k份,每一份都有一个1之后将剩余的分成k份,这样就可以保证每一份都不为1)

    for(int i=1;i<=n;i++)
    	for(int j=1;j<=n;j++) {
    		if(j==1) dp[i][j]=1;
    		else dp[i][j]=dp[i-1][j-1]+dp[i-j][j];
    	}
    		 
    
    
  • 相关阅读:
    GDI 设备环境句柄(2)
    GDI 像素(5)
    Api+Mvc增删查改
    sql语句全
    Mvc 导出
    触发器、事务
    计算时间戳的差
    SQL行转列经典例子(转载)
    Socket (套接字)通信
    MVC上传图片
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13598554.html
Copyright © 2011-2022 走看看