nyoj571 整数划分(三)

9 篇文章 0 订阅

这道题吃了翔,尼玛有没说多case。。。

通过这道题,初步了解了整数划分的经典问题,值得。


一 求将n划分为若干正整数之和的划分数

 

1. 若划分的多个整数可以相同

  设dp[i][j]为将i划分为不大于j的划分数

  (1) 当i<j 时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i]

  (2) 当i>j 时,可以根据划分中是否含有j分为两种情况。若划分中含有j,划分方案数为dp[i-j][j];若划分数中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>jdp[i][j]=dp[i-j][j]+dp[i][j-1]

  (3) 当i=j 时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]

dp[n][n]可以解决问题1dp[n][k]表示将n划分为最大数不超过k的划分数,可以解决问题3


2. 若划分的正整数必须不同

  设dp[i][j]为将i划分为不超过j的不同整数的划分数

  (1) 当i<j时,i不能划分为大于i的数,所以dp[i][j]=dp[i][i]

  (2) 当i>j时,可以根据划分中是否含有j分为两种情况。若划分中含有j,则其余的划分中最大只能是j-1,方案数为dp[i-j][j-1];若划分中不含j,相当于将i划分为不大于j-1的划分数,为dp[i][j-1]。所以当i>jdp[i][j]=dp[i-j][j-1]+dp[i][j-1]

  (3) 当i=j时,若划分中含有j只有一种情况,若划分中不含j相当于将i划分为不大于j-1的划分数。此时dp[i][j]=1+dp[i][j-1]


dp[n][n]表示将n划分为不同整数的划分数,可以解决问题5.

 

二 将n划分为k个整数的划分数

dp[i][j]为将i划分为j个整数的划分数。

  (1) i<j为不可能出现的情况,dp[i][j]=0

  (2) 若i=j,有一种情况:i可以划分为i1之和,dp[i][j]=1

  (3) 若i>j,可以根据划分数中是否含有1分为两类:若划分数中含有1,可以使用“截边法”将j个划分分别截去一个1,把问题转化为i-jj-1个划分数,为dp[i-j][j-1]; 若划分中不包含1,使用“截边法”将j个划分数的最下面一个数截去,将为题转化为求i-jj个划分数,为dp[i-j][j]。所以i>jdp[i][j]=dp[i-j][j-1]+dp[i-j][j]


dp[n][k]为将n划分为k个整数的划分数,可解决问题2


 

三 将n划分为若干正奇数之和的划分数

 

f[i][j]为将i划分为j个奇数之和的划分数,g[i][j]为将i划分为j个偶数之和的划分数。

使用截边法,将g[i][j]j个划分都去掉1,可以得到f[i-j][j],所以

g[i][j] = f[i-j][j]

f[i][j]中有包含1的划分方案和不包含1的划分方案。对于包含1的划分方案,可以将1的划分除去,转化为“将i-1划分为j-1个奇数之和的划分数”,即f[i-1][j-1];对于不包含1的划分方案,可以使用截边法对j个划分每一个都去掉一个1,转化为“将i-j划分为j个偶数之和的划分数”,即g[i-j][j]

所以f[i][j]=f[i-1][j-1]+g[i-j][j]

f[n][0]+f[n][1]+……+f[n][n]为将n划分为若干奇数的划分数,为问题4的答案。


四   将正整数划分成连续的正整数之和

如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。



整数划分代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define N 1001
int dp[N][N],fp[N][N];
int n,k;
void divide1()        //将i划分为不大于j的划分数
{
    for(int i=1; i<=n; i++)
        dp[i][1]=1;
    for(int i=1; i<=n; i++)
    {
        for(int j=2; j<=n; j++)
        {
            if(i<j) dp[i][j]=dp[i][i];
            else if(i==j) dp[i][j]=1+dp[i][j-1];
            else
                dp[i][j]=dp[i-j][j]+dp[i][j-1];
        }
    }
}
void divide2()        //将i划分为不大于j的不同整数的划分数
{
    dp[1][1]=1;
    for(int i=2; i<=n; i++)
        dp[i][1]=0;
    for(int i=1; i<=n; i++)
    {
        for(int j=2; j<=n; j++)
        {
            if(i<j) dp[i][j]=dp[i][i];
            else if(i==j)  dp[i][j]=1+dp[i][j-1];
            else
                dp[i][j]=dp[i-j][j-1]+dp[i][j-1];
        }
    }
}
void divide3()     //dp[i][j]表示将i划分为j个整数之和的划分数
{
    for(int i=1; i<=n; i++)
        dp[i][1]=1;
    for(int i=1; i<=n; i++)
    {
        for(int j=2; j<=n; j++)
        {
            if(i<j) dp[i][j]=0;
            else if(i==j) dp[i][j]=1;
            else
            {
                dp[i][j]=dp[i-j][j]+dp[i-1][j-1];
            }
        }
    }
}
int divide4()  //dp[i][j]表示将i划分为j个奇数的划分数
//fp[i][j]表示将i划分为j个偶数的划分数
{
    dp[0][0]=fp[0][0]=1;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=i; j++)
        {
            fp[i][j]=dp[i-j][j];
            dp[i][j]=dp[i-1][j-1]+fp[i-j][j];
        }
    int sum=0;
    for(int i=1; i<=n; i++)
        sum+=dp[n][i];
    return sum;
}
int main()
{
    //FILE *fp;
    //int at,bt,ct,dt,et;
    //fp=fopen("H:\\codeblocks文件\\data.txt","r");
    while(cin >>n>>k){
    int a,b,c,d,e;
    divide1();
    a=dp[n][n];
    c=dp[n][k];
    divide2();
    e=dp[n][n];
    divide3();
    b=dp[n][k];
    d=divide4();
    //fscanf(fp,"%d%d%d%d%d",&at,&bt,&ct,&dt,&et);
    //if(at!=a||bt!=b||ct!=c||dt!=d||et!=e)
    //cout<<1<<endl;
    cout<<a<<endl<<b<<endl<<c<<endl<<d<<endl<<e<<endl<<endl;
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值