zoukankan      html  css  js  c++  java
  • 7215:简单的整数划分问题

    简介: 总时间限制: 100ms 内存限制: 65536kB描述将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。正整数n 的这种表示称为正整数n 的划分。

    总时间限制: 100ms 内存限制: 65536kB
    描述
    将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
    正整数n 的这种表示称为正整数n 的划分。正整数n 的不同的划分个数称为正整数n 的划分数。

    输入
    标准的输入包含若干组测试数据。每组测试数据是一个整数N(0 < N <= 50)。
    输出
    对于每组测试数据,输出N的划分数。
    样例输入
    5
    样例输出
    7
    提示
    5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1

    注意对比题目:

    (1)数的划分(把n分为k份,有点类似于放苹果,但放苹果这道题目里面盘子可以为空,它不允许为空。)

    (2)复杂的整数划分 

    算法(一)递归法

    把一个正整数n写成如下形式: 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和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以达到回归条件,从而解决问题。其递推表达式如下:
    f(n, m)    =1;            (n=1 or m=1)
                   =f(n, n);          (n<m)
                   =1+ f(n, m-1);           (n=m)
                   =f(n-m,m)+f(n,m-1);       (n>m)

     1 #include <stdio.h>
     2 long long GetPartitionCount(int n,int max)
     3 {
     4     if(n==1||max==1) return 1;
     5     else if(n<max) return GetPartitionCount(n,n);
     6     else if(n==max) return 1+GetPartitionCount(n,max-1);
     7     else return GetPartitionCount(n,max-1)+GetPartitionCount(n-max,max);
     8 }
     9 int main(int argc, char *argv[])
    10 {
    11     int n;
    12     while(scanf("%d",&n)!=EOF)
    13     {
    14         printf("%lld\n",GetPartitionCount(n,n));
    15     }
    16     return 0;
    17 }

    算法(二)动态规划

    这道题就是整数的划分,其实定义好了状态,就是简单的动态规划的递推。

    我们定义dp[n][k]表示将n进行划分,最大的数不超过k的方案有多少种,那么我们可以得到如下的递推方案:

    dp[n][k] = dp[n][k-1] + dp[n-k][k];

    其中的dp[n][k-1]便是将n进行进行整数的划分,最大的数不超过k-1的方案数;dp[n-k][k]表示拿出一个k后,剩下的数被不超过k的数的表示的方案数。

    其中当k>n的时候,则和dp[n][n]的值相同,下面通过递推的方式求出所有的解,然后对应的输入n,输出dp[n][n]就行了。 

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdlib>
     4 #include<cstdio>
     5 using namespace std;
     6 
     7 const int MAX = 122;  
     8 int dp[MAX][MAX];
     9 
    10 void Dynamic()
    11 {
    12     for(int i=1; i<MAX; i++)
    13     {
    14         dp[i][1] = dp[1][i] = dp[0][i] = 1;
    15     }
    16     for(int i=2; i<MAX; i++)
    17     {
    18         for(int j=2; j<MAX; j++)
    19         {
    20             if(j<=i) dp[i][j] = dp[i][j-1] + dp[i-j][j];
    21             else dp[i][j] = dp[i][i];
    22         }
    23     }
    24 }  
    25 int main()  
    26 {  
    27     int n;
    28     Dynamic();
    29     while(scanf("%d",&n)!=EOF)
    30     {
    31         printf("%d\n",dp[n][n]);
    32     }
    33     return 0;  
    34 }

     这道题的动规思路,下面的论述比较清晰一些:

    整数划分问题 
    数 n 的划分是将 n 表示成多个正整数之和的形式,划分可以分为两种情况:
    第一种情况:划分的多个正整数中,正整数的数量是任意的。

    这又可以分为划分的正整数中,正整数可以相同与不同两类

     1.  划分的多个正整数可以相同, 递推方程可以表示为:
         (1)   dp[n][m]= dp[n][m-1]+ dp[n-m][m]
               分析dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。则划分数可以分为以下两种情况:
               a. 划分中每个数都小于 m, 相当于每个数不大于 m- 1, 故划分数为 dp[n][m-1]. 
               b. 划分中有至少一个数为 m. 那就在 n中减去 m , 剩下的就相当于把 n-m 进行划分, 故划分数为 dp[n-m][m];
          (2)   dp[n][m]= dp[n][m+1]+ dp[n-m][m]
                其中dp[n][m]表示整数 n 的划分中,每个数不小于 m 的划分数。同理可证明该式。
     
     2.  划分的多个正整数互不相同,递推方程可以表示为:
         (1)    dp[n][m]= dp[n][m-1]+ dp[n-m][m-1]
                分析: dp[n][m]表示整数 n 的划分中,每个数不大于 m 的划分数。同样划分情况分为以下两种情况:
                 a.  划分中每个数都小于 m, 相当于每个数不大于 m- 1,划分数为 dp[n][m-1].
                 b.  划分中有一个数为 m. 在 n 中减去 m, 剩下相当对n- m 进行划分,并且每一个数不大于 m- 1,故划分数为 dp[n-m][m-1]
         (2)    dp[n][m]= dp[n][m+1]+ dp[n-m][m]
                 其中dp[n][m]表示整数 n 的划分中,每个数不小于 m 的划分数。

    第二种情况:划分的多个正整数中,正整数的数量是固定的
       把一个整数 n 无序划分成 k 份互不相同的正整数之和的方法总数。   方程为: dp[n][k]= dp[n-k][k]+ dp[n-1][k-1];
       证明方法参考: http://www.mydrs.org/program/html/0369.htm
       另一种理解,总方法可以分为两类:
       第一类: n 份中不包含 1 的分法,为保证每份都 >= 2,可以先拿出 k 个 1 分
       到每一份,然后再把剩下的 n- k 分成 k 份即可,分法有: dp[n-k][k]
       第二类: n 份中至少有一份为 1 的分法,可以先那出一个 1 作为单独的1份,剩
       下的 n- 1 再分成 k- 1 份即可,分法有:dp[n-1][k-1]

    类似问题:

    M个小球装N个盒子,或者苹果装盘问题。比如:把M个球放到N个盒子,允许有空的盒子(不放球),有多少种放法?这些都属于典型的DP问题。

    用F(m,n)表示有多少种放法:
        如果m=0 或者 m=1 , F = 1
        如果n=0 或者 n=1   , F =1
        既F(0,0) = F(0,1) = F(1,0) = F(1,1) = 1
        否则 F = F(m-n,n) + F(m,n-1)这就是DP的解空间递归解

    关于整数的质因子和分解

    【问题描述】
    歌德巴赫猜想说任何一个不小于6的偶数都可以分解为两个奇素数之和。对此问题扩展,如果一个整数能够表示成两个或多个素数之和,则得到一个素数和分解式。对于一个给定的整数,输出所有这种素数和分解式。注意,对于同构的分解只输出一次(比如5只有一个分解2 + 3,而3 + 2是2 + 3的同构分解式)。

    例如,对于整数8,可以作为如下三种分解:
    (1) 8 = 2 + 2 + 2 + 2
    (2) 8 = 2 + 3 + 3
    (3) 8 = 3 + 5

    【算法分析】
    由于要将指定整数N分解为素数之和,则首先需要计算出该整数N内的所有素数,然后递归求解所有素数和分解即可。

    原作者的C++代码:(感觉其实就是回溯的思路)

     1 #include <iostream>   
     2 #include <vector>   
     3 #include <iterator>   
     4 #include <cmath>   
     5 using namespace std;   
     6   
     7 // 计算num内的所有素数(不包括num)   
     8 void CalcPrimes(int num, vector<int> &primes)   
     9 {   
    10     primes.clear();   
    11     if (num <= 2)   
    12         return;   
    13        
    14     primes.push_back(2);   
    15     for (int i = 3; i < num; i += 2) 
    16     {   
    17         int root = int(sqrt(i));   
    18         int j = 2;   
    19         for (j = 2; j <= root; ++j)
    20         {   
    21             if (i % j == 0)   
    22                 break;   
    23         }   
    24         if (j > root)   
    25             primes.push_back(i);   
    26     }   
    27 }   
    28   
    29 // 输出所有素数组合(递归实现)   
    30 int PrintCombinations(int num, const vector<int> &primes, int from, vector<int> &numbers)   
    31 {   
    32     if (num == 0) 
    33     {   
    34         cout << "Found: ";   
    35         copy(numbers.begin(), numbers.end(), ostream_iterator<int>(cout, " "));   
    36         cout << '\n';   
    37         return 1;   
    38     }   
    39        
    40     int count = 0;   
    41        
    42     // 从第from个素数搜索,从而避免输出同构的多个组合   
    43     int primesNum = primes.size();   
    44     for (int i = from; i < primesNum; ++i)
    45     {   
    46         if (num < primes[i])   
    47             break;   
    48         numbers.push_back(primes[i]);   
    49         count += PrintCombinations(num - primes[i], primes, i, numbers);   
    50         numbers.pop_back();   
    51     }   
    52        
    53     return count;   
    54 }   
    55   
    56 // 计算num的所有素数和分解   
    57 int ExpandedGoldbach(int num)   
    58 {   
    59     if (num <= 3)   
    60         return 0;   
    61        
    62     vector<int> primes;   
    63     CalcPrimes(num, primes);   
    64        
    65     vector<int> numbers;   
    66     return PrintCombinations(num, primes, 0, numbers);   
    67 }   
    68   
    69 int main()   
    70 {   
    71     for (int i = 1; i <= 20; ++i)
    72     {   
    73         cout << "When i = " << i << ":\n";   
    74         int count = ExpandedGoldbach(i);   
    75         cout << "Total: " << count << "\n\n";   
    76     }   
    77 }
    View Code

    C语言代码如下:

     1 #include<stdio.h>
     2 #include<math.h>
     3 #include<string.h>
     4 
     5 #define maxNum 1000
     6 int primeNum[maxNum]={0},ansArr[maxNum]={0};
     7 int indexForPrimeNum,indexForAnsArr;
     8 
     9 int work(int num);//输出num的所有素数和分解的方案并返回其总方案数
    10 int CalcPrimes(int num);//计算num内的所有素数(不包括num),结果保存在primeNum[]. 
    11 
    12 int PrintAns(int num,int from);
    13 //回溯法的思想:从primeNum[]的第from个元素开始选择元素来累加构造num。
    14 //构造结果放在ansArr[]中。寻找到一个构造方案后输出该方案.
    15 //最终返回总的方案数
    16 
    17 int main(int argc, char *argv[])
    18 {
    19     for (int i = 1; i <= 20; ++i)
    20     {   
    21         printf("When i = %d:\n",i);   
    22         int count = work(i);   
    23         printf("Total: %d\n\n",count);   
    24     }
    25     return 0;
    26 }
    27 //计算num内的所有素数(不包括num),结果保存在primeNum[]. 返回数组元素个数 
    28 int CalcPrimes(int num)
    29 {
    30     int i,j,root,k=0;
    31     
    32     if(num<2) return k;
    33     memset(primeNum,0,sizeof(primeNum));
    34     
    35     primeNum[k++]=2;
    36     for(i=3;i<num;i++)
    37     {
    38         root=sqrt(i);
    39         for(j=2;j<=root;j++)
    40         {
    41             if(i%j==0) break;
    42         }
    43         if(j>root) primeNum[k++]=i;
    44     }
    45     return k;
    46 }
    47 //调用PrintAns()输出num的所有素数和分解的方案,返回其总方案数 
    48 int work(int num)
    49 {
    50     if(num<=3) return 0;
    51     
    52     indexForPrimeNum=CalcPrimes(num);
    53     
    54     memset(ansArr,0,sizeof(ansArr));
    55     indexForAnsArr=0;
    56     return PrintAns(num,0);
    57 }
    58 
    59 //回溯法的思想:从primeNum[]的第from个元素开始选择元素来累加构造num。
    60 //构造结果放在ansArr[]中。寻找到一个构造方案后输出该方案.
    61 //最终返回总的方案数
    62 int PrintAns(int num,int from)
    63 {
    64     int i;
    65     if(num==0)
    66     {
    67         printf("Found: ");
    68         for(i=0;i<indexForAnsArr;i++) printf(" %d",ansArr[i]);
    69         printf("\n");
    70         return 1;
    71     }
    72     
    73     int totalCount = 0;
    74     //从第from个素数搜索,从而避免输出同构的多个组合
    75     for(i=from;i<indexForPrimeNum;i++)
    76     {
    77         if(num<primeNum[i]) break;
    78         ansArr[indexForAnsArr++]=primeNum[i];
    79         totalCount+=PrintAns(num-primeNum[i],i);
    80         indexForAnsArr--;    
    81     }
    82     return totalCount;
    83 }
    View Code

    参考:

    http://www.cppblog.com/superKiki/archive/2010/05/27/116506.html

    http://blog.csdn.net/geniusluzh/article/details/8118683

  • 相关阅读:
    NPOI创建DOCX常用操作
    【Python】django多对多 查询 ,反查等操作
    【Python】python 普通继承方式和super继承方式
    【云计算】开源装机自动化系统 CloudBoot OSInstall 介绍
    【Python】Python AES 对称加密示例
    【Python】Django 如何直接返回404 被 curl,wget 捕获到
    【Python】Django 支持 restful 风格 url
    【Python】Django 聚合 Count与Sum用法,注意点
    【Python】使用 boto 调用 S3 对象存储API
    【Other】千字文 硬笔 楷书 字帖
  • 原文地址:https://www.cnblogs.com/gongxianjin/p/15787038.html
Copyright © 2011-2022 走看看