DP经典 ----- 整数划分

来源:互联网 发布:c 界面编程 编辑:程序博客网 时间:2024/05/19 20:21

整数划分 --- 一个老生长谈的问题:
  1) 练练组合数学能力.
  2) 练练递归思想
  3) 练练DP
  总之是一道经典的不能再经典的题目:
  这道好题求:
  1. 将n划分成若干正整数之和的划分数。
  2. 将n划分成k个正整数之和的划分数。
  3. 将n划分成最大数不超过k的划分数。
  4. 将n划分成若干奇正整数之和的划分数。
  5. 将n划分成若干不同整数之和的划分数。

 

1.将n划分成不大于m的划分法: 

   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-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.将n划分成k个数的划分法:

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

     方法可以分为两类:
       第一类: 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]

  

  3.将n划分成若干奇数的划分法:(不懂)

    g[i][j]:将i划分为j个偶数

    f[i][j]:将i划分为j个奇数
     g[i][j] = f[i - j][j];
     f[i][j] = f[i - 1][j - 1] + g[i - j][j];

 

    路过的大牛求解释,谢谢~

  代码如下所示:

复制代码
/*
* hit1402.c
*
* Created on: 2011-10-11
* Author: bjfuwangzhu
*/

#include<stdio.h>
#include<string.h>
#define nmax 51
int num[nmax][nmax]; //将i划分为不大于j的个数
int num1[nmax][nmax]; //将i划分为不大于j的不同的数
int num2[nmax][nmax]; //将i划分为j个数
int f[nmax][nmax]; //将i划分为j个奇数
int g[nmax][nmax]; //将i划分为j个偶数
void init() {
int i, j;
for (i = 0; i < nmax; i++) {
num[i][0] = 0, num[0][i] = 0, num1[i][0] = 0, num1[0][i] = 0, num2[i][0] =
0, num2[0][i] = 0;
}
for (i = 1; i < nmax; i++) {
for (j = 1; j < nmax; j++) {
if (i < j) {
num[i][j] = num[i][i];
num1[i][j] = num1[i][i];
num2[i][j] = 0;
} else if (i == j) {
num[i][j] = num[i][j - 1] + 1;
num1[i][j] = num1[i][j - 1] + 1;
num2[i][j] = 1;

} else {
num[i][j] = num[i][j - 1] + num[i - j][j];
num1[i][j] = num1[i][j - 1] + num1[i - j][j - 1];
num2[i][j] = num2[i - 1][j - 1] + num2[i - j][j];
}
}
}
f[0][0] = 1, g[0][0] = 1;
for (i = 1; i < nmax; i++) {
for (j = 1; j <= i; j++) {
g[i][j] = f[i - j][j];
f[i][j] = f[i - 1][j - 1] + g[i - j][j];
}
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("data.in", "r", stdin);
#endif
int n, k, i, res0, res1, res2, res3, res4;
init();
while (~scanf("%d %d", &n, &k)) {
res0 = num[n][n];
res1 = num2[n][k];
res2 = num[n][k];
for (i = 0, res3 = 0; i <= n; i++) {
res3 += f[n][i];
}
res4 = num1[n][n];
printf("%d\n%d\n%d\n%d\n%d\n\n", res0, res1, res2, res3, res4);
}
return 0;
}
复制代码


将正整数划分成连续的正整数之和
如15可以划分成4种连续整数相加的形式:
15
7 8
4 5 6
1 2 3 4 5

    首先考虑一般的形式,设n为被划分的正整数,x为划分后最小的整数,如果n有一种划分,那么
结果就是x,如果有两种划分,就是x和x x + 1, 如果有m种划分,就是 x 、x x + 1 、 x x + 1 x + 2 、... 、x x + 1 x + 2 ... x + m - 1
将每一个结果相加得到一个公式(i * x + i * (i - 1) / 2) = n,i为当前划分后相加的正整数个数。
满足条件的划分就是使x为正整数的所有情况。
如上例,当i = 1时,即划分成一个正整数时,x = 15, 当i = 2时, x = 7。
当x = 3时,x = 4, 当x = 4时,4/9,不是正整数,因此,15不可能划分成4个正整数相加。
当x = 5时,x = 1。

    这里还有一个问题,这个i的最大值是多少?不过有一点可以肯定,它一定比n小。我们可以做一个假设,
假设n可以拆成最小值为1的划分,如上例中的1 2 3 4 5。这是n的最大数目的划分。如果不满足这个假设,
那么 i 一定比这个划分中的正整数个数小。因此可以得到这样一个公式i * (i + 1) / 2 <= n,即当i满足
这个公式时n才可能被划分。

代码如下:

复制代码
void split(int n) {
int i, j, te, x, xlen;
for (i = 1, xlen = 0; (te = i * (i - 1) / 2) < n; i++) {
x = n - te;
if (x % i == 0) {
x /= i;
printf("%d", x);
for (j = 1; j < i; j++) {
printf("%d ", x + j);
}
printf("\n");
xlen++;
}
}
printf("%d\n", xlen);
}
复制代码


 

以下是转载的:

  求划分因子乘积最大的一个划分及此乘积
  问题简述:给定一个正整数n, 则在n所有的划分中, 求因子乘积最大的一个划分及此乘积。例如:8 = {8}, {7, 1}, {6, 2}, {5, 3}, {4, 4}, {3, 3, 2}, {2, 2, 2, 2} 等,那么在这些当中,3 * 3 * 2 的乘积最大,所以输出整个划分
和这个乘积 18。
  算法分析:这是我在某个论坛上看到的问题,以及别人针对此问题的数学分析,现简单的整理如下:
  (1)对于任意大于等于4的正整数m, 存在一个划分m = m1+m2, 使 m1*m2 >= m证: 令m1 = int(m/2), 则 m1 >= 2 , m2 = m-m1; 那么m2 > 2,并且 m2 >= m/2 >= m1;    m1*m2 >= 2*m2 >= m; 证毕;
该证明简单的来说就是:对于一个大于等于4的正整数m,存在一个2块划分的因子,这两个因子的乘积总是不小于原数m本身。
  (2)由(1)知此数最终可以分解为 2^r * 3^s。现证明 r <= 2;
  证:若r > 2, 则至少有3个因子为2, 而2*2*2 < 3*3;
  所以可以将3个为2的因子,换为两个因子3;积更大;证毕。
  综合(1),(2),则有:任何大于4的因子都可以有更好的分解, 而4可以分解为2*2。
  所以:此数应该分解为 2^k1 * 3^k2。而且可以证明 k1>=0 并且 k1 <= 2,因此:
     A.当n = 3*r 时, 分解为 3^r
     B.当n = 3*r+1时, 分解为 3^(r-1)*2*2
     C.当n = 3*r+2时, 分解为 3^r*2
  剩下编程处理,那就是太简单了,首先是处理 <= 4的特殊情况,再对>4的情况进行模3的3种情况的判断,最后一一输出。可见,数学在整数划分问题上有太强的功能。谁叫这个问题叫整数划分呢,不与数学密切才怪! ^_^。

 

  小学六年级奥数---整数划分(有用结论)

  例1:把14分拆成若干个自然数的和,再求出这些数的积,要使得到的积最大,应该把14如何分拆?这个最大的乘积是多少?


  分析与解:我们先考虑分成哪些数时乘积才能尽可能地大。
  首先,分成的数中不能有1,这是显然的。
  其次,分成的数中不能有大于4的数,否则可以将这个数再分拆成2与另外一个数的和,这两个数的乘积一定比原数大,例如7就比它分拆成的2和5的乘积小。
  再次,因为4=2×2,故我们可以只考虑将数分拆成2和3。
  注意到2+2+2=6,2×2×2=8;3+3=6,3×3=9,因此分成的数中若有三个2,则不如换成两个3,换句话说,分成的数中至多只能有两个2,其余都是3。根据上面的讨论,我们应该把14分拆成四个3与一个2之和,即14=3+3+3+3+2,这五数的积有最大值 3×3×3×3×2=162。
  将上述结论推广为一般情形便是:
  把自然数S(S>1)分拆为若干个自然数的和:   S=a1+a2+…+an,则当a1,a2,…,an中至多有两个2,其余都是3时,其连乘积m=a1a2…an有最大值。

  例2:把1993分拆成若干个互不相等的自然数的和,且使这些自然数的乘积最大,该乘积是多少?
解:由于把1993分拆成若干个互不相等的自然数的和的分法只有有限种,因而一定存在一种分法,使得这些自然数的乘积最大。
  若1作因数,则显然乘积不会最大。把1993分拆成若干个互不相等的自然数的和,因数个数越多,乘积越大。为了使因数个数尽可能地多,我们把1993分成2+3…+n直到和大于等于1993。
若和比1993大1,则因数个数至少减少1个,为了使乘积最大,应去掉最小的2,并将最后一个数(最大)加上1。
若和比1993大k(k≠1),则去掉等于k的那个数,便可使乘积最大。
所以n=63。因为2015-1993=22,所以应去掉22,把1993分成(2+3+…+21)+(23+24+…+63)


这一形式时,这些数的乘积最大,其积为  2×3×…×21×23×24×…×63。


转自acdreamer

五边形定理

设第n个五边形数为,那么,即序列为:1, 5, 12, 22, 35, 51, 70, ...

 

对应图形如下:

 

 

设五边形数的生成函数为,那么有:

 

 

 

 

以上是五边形数的情况。下面是关于五边形数定理的内容:

 

五边形数定理是一个由欧拉发现的数学定理,描述欧拉函数展开式的特性。欧拉函数的展开式如下:

 

 

 

欧拉函数展开后,有些次方项被消去,只留下次方项为1, 2, 5, 7, 12, ...的项次,留下来的次方恰为广义五边形数。

 

 

五边形数和分割函数的关系

 

欧拉函数的倒数是分割函数的母函数,亦即:

 

   其中为k的分割函数。

 

上式配合五边形数定理,有:

 

 

 
在 n>0 时,等式右侧的系数均为0,比较等式二侧的系数,可得
 

p(n) - p(n-1) - p(n-2) + p(n-5) + p(n-7) + \cdots=0

 

因此可得到分割函数p(n)的递归式:p(n) = p(n-1) + p(n-2) - p(n-5) - p(n-7) + \cdots

 

例如n=10时,有:p(10) = p(9) + p(8) - p(5) - p(3) = 30 + 22 - 7 -  3 = 42

 

 

所以,通过上面递归式,我们可以很快速地计算n的整数划分方案数p(n)了。

 

 

题目: http://acm.hdu.edu.cn/showproblem.php?pid=4651

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. typedef long long LL;  
  7.   
  8. const int N=100005;  
  9. const LL MOD=1000000007;  
  10.   
  11. LL ans[N],tmp[N];  
  12.   
  13. void Init()  
  14. {  
  15.     int t=1000;  
  16.     for(int i=-1000;i<=1000;i++)  
  17.         tmp[i+t]=i*(3*i-1)/2;  
  18.     ans[0]=1;  
  19.     for(int i=1;i<N;i++)  
  20.     {  
  21.         ans[i]=0;  
  22.         for(int j=1;j<=i;j++)  
  23.         {  
  24.             if(tmp[j+t]<=i)  
  25.             {  
  26.                 if(j&1)  ans[i]+=ans[i-tmp[j+t]];  
  27.                 else     ans[i]-=ans[i-tmp[j+t]];  
  28.             }  
  29.             else break;  
  30.             ans[i]=(ans[i]%MOD+MOD)%MOD;  
  31.             if(tmp[t-j]<=i)  
  32.             {  
  33.                 if(j&1) ans[i]+=ans[i-tmp[t-j]];  
  34.                 else    ans[i]-=ans[i-tmp[t-j]];  
  35.             }  
  36.             else break;  
  37.         }  
  38.         ans[i]=(ans[i]%MOD+MOD)%MOD;  
  39.     }  
  40. }  
  41. int main()  
  42. {  
  43.     int t,n;  
  44.     Init();  
  45.     cin>>t;  
  46.     while(t--)  
  47.     {  
  48.         cin>>n;  
  49.         cout<<ans[n]<<endl;  
  50.     }  
  51.     return 0;  
  52. }  


 

题目:http://acm.hdu.edu.cn/showproblem.php?pid=4658

 

题意:问一个数n能被拆分成多少种方法,且每一种方法里数字重复个数不能超过k(等于k)。
 
分析递推式为:
 
 
 
[cpp] view plain copy
  1. #include <iostream>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4.   
  5. using namespace std;  
  6. const int N = 100005;  
  7. const int MOD = 1000000007;  
  8.   
  9. int dp[N];  
  10.   
  11. void Init()  
  12. {  
  13.     dp[0] = 1;  
  14.     for(int i=1;i<N;i++)  
  15.     {  
  16.         dp[i] = 0;  
  17.         for(int j=1;;j++)  
  18.         {  
  19.             int t = (3*j-1)*j / 2;  
  20.             if(t > i) break;  
  21.             int tt = dp[i-t];  
  22.             if(t+j <= i) tt = (tt + dp[i-t-j])%MOD;  
  23.             if(j&1) dp[i] = (dp[i] + tt)%MOD;  
  24.             else    dp[i] = (dp[i] - tt + MOD)%MOD;  
  25.         }  
  26.     }  
  27. }  
  28.   
  29. int Work(int n,int k)  
  30. {  
  31.     int ans = dp[n];  
  32.     for(int i=1;;i++)  
  33.     {  
  34.         int t = k*i*(3*i-1) / 2;  
  35.         if(t > n) break;  
  36.         int tt = dp[n-t];  
  37.         if(t + i*k <= n) tt = (tt + dp[n-t-i*k])%MOD;  
  38.         if(i&1) ans = (ans - tt + MOD)%MOD;  
  39.         else    ans = (ans + tt)%MOD;  
  40.     }  
  41.     return ans;  
  42. }  
  43.   
  44. int main()  
  45. {  
  46.     Init();  
  47.     int n,k,t;  
  48.     scanf("%d",&t);  
  49.     while(t--)  
  50.     {  
  51.         scanf("%d%d",&n,&k);  
  52.         printf("%d\n",Work(n,k));  
  53.     }  
  54.     return 0;  
  55. }  

0 0