整数划分问题 【经典DP】

相关题目1
相关题目2
相关题目3

下面的描述大部分借鉴于(https://blog.csdn.net/Lin_disguiser/article/details/50574818), 感谢, 但是其中有部分错误, 我会在下面的描述中纠正过来…

总的解决方法时截边法, 也就是去讨论有1无1的情况和截去他们的情况….. 记住了.

一 求将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>j时dp[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]

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>j时dp[i][j]=dp[i-j][j-1]+dp[i][j-1];(与上面的的转移方程唯一不同的地方, dp[i-j][j-1])

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

dp[n][n]表示将n划分为不同整数的划分数

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

设dp[i][j]为将i恰好划分为j个整数的划分数 (解答描述已纠正)

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

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

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

三 将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] (因为总的考虑1的情况, 所以注重讨论奇数的情况)

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划分为若干奇数的划分数的方案数.

四 将n划分为不多于k个正整数的划分数

设dp[i][j] 代表 i 划分为不多于j个正整数的划分数
所以:
(1) 当 i == 1 || j == 1时, dp[i][j] = 1; 前面为只有1这个情况, 后面为划分为自身的情况.
(2) 当 i == j 时, dp[i][j] = dp[i][j-1] + 1; 表示将i划分为不多于j-1个的方案数, + 全是1组成i的情况.
(3) 当 i < j 时, 没有多余方案, dp[i][j] = dp[i][i];
(4) 当 i > j 时, 分两种齐情况, 一是如果这不多于j个正整数中有1,那么可以将这个1合并到任意一个数字中, 也就是求i 划分为不多于j-1个正整数的方案, 而是如果 这不多于j个正整数中没有1, 那么可以利用截边法将每一个数字截去1, 那么就是求截去1的方案数也就是dp[i-j][j]; 所以转移方程为:
dp[i][j] = dp[i][j-1] + dp[i-j][j];

下面就是上面这些所有的问题解决模板了: 需要哪个用哪个. 唯一坑点就是可能数据量较大 , 需要long long.

ll dp[maxn][maxn];
ll f[maxn][maxn], g[maxn][maxn];
int n, k;
void div1() {
    // dp[i][j] 代表将i划分为不大于j的划分数(允许重复数字)
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= k ; j ++) {
            if (j == 1) dp[i][j] = 1;
            else 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];
        }
    }
}
void div2() {
    // dp[i][j] 代表将i划分为不大于j的划分数(没有重复数字)
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= k ; j ++) {
            if (j == 1) {
                if (i == 1) dp[i][j] = 1;
                else dp[i][j] = 0; // 初始化不同即可, 转移方程依旧相同.
            }
            else 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];
        }
    }
}
void div3() {
    // dp[i][j]为将i恰好划分为j个整数的划分数
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= k ; j ++) {
            if (i == j) dp[i][j] = 1;
            else if (i < j) dp[i][j] = 0;
            else dp[i][j] = dp[i-1][j-1] + dp[i-j][j];
        }
    }
}
void div4() {
    //设f[i][j]为将i恰好划分为j个奇数之和的划分数
    // g[i][j]为将i恰好划分为j个偶数之和的划分数。
    f[0][0] = g[0][0] = 1;
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= k ; j ++) {
            if (i < j) f[i][j] = g[i][j] = 0;
            else {
                f[i][j] = f[i-1][j-1] + g[i-j][j];
                g[i][j] = f[i-j][j];
            }
        }
    }
}
void div5() {
    // dp[i][j] 代表i划分为不多于j个正整数的划分数
    for (int i = 1 ; i <= n ; i ++) {
        for (int j = 1 ; j <= k ; j ++) {
            if (i == 1 || j == 1) dp[i][j] = 1;
            else 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];
        }
    }
}

代码都是可以通过上面的问题的….. 所以下次遇到了直接取就行……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值