DP求方案数类题目汇总


DP求方案数类题目汇总

P1164 小A点菜

【题目链接】P1164 小A点菜 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:经典01背包求方案数问题(恰好)

f[i][j]:从前i个物品中选,总和恰好为j的所有方案的集合。

属性:count

f[i][j] = f[i - 1][j] + f[i - 1][j - w]

初始化

f[0][0] = 1;

【代码实现】

二维:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N][N];// 从前i个物品中选,总和恰好为j的所有方案的集合
int n, m;

int main()
{
    cin >> n >> m;
    
    f[0][0] = 1;// 一个都不选
    for (int i = 1; i <= n; i ++ )
    {
        int w;
        cin >> w;
        for(int j = 0; j <= m; j ++)
        {
            f[i][j] = f[i - 1][j];
            if(j - w >= 0) f[i][j] += f[i - 1][j - w];
        }
    }
    cout << f[n][m];
    return 0;
}

一维:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N];// 从前i个物品中选,总和恰好为j的所有方案的集合
int n, m;

int main()
{
    cin >> n >> m;
    
    f[0] = 1;// 一个都不选
    for (int i = 1; i <= n; i ++ )
    {
        int w;
        cin >> w;
        for(int j = m ; j >= w; j --)
        {
            if(j - w >= 0) f[j] += f[j - w];
        }
    }
    cout << f[m];
    return 0;
}

方格取数

【题目链接】2067. 走方格 - AcWing题库
思路:数字三角形模型的计数问题

f[i][j]:从(1,1)走到(n,m)的所有可行方案的集合

属性:count

初始化

 f[1][1] = 1;

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 40;
int f[N][N];
int n, m;

int main()
{
    cin >> n >> m;
    
    f[1][1] = 1;// 初始化
    for (int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++)
        {
            if(i == 1 && j == 1) continue;// 不能少!!!
            if(i % 2 ==0 && j % 2 == 0) continue;
            f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    cout << f[n][m];    
    return 0;
}

思路二:dfs(数据量小嘛)—— 部分分数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 40;

int n, m;
int ans;

void dfs(int x, int y)
{
    if(x % 2 == 1 || y % 2 == 1)// 至少有一项为奇数,说明可以走该点
    {
        if(x == n && y == m)// 走到终点
        {
            ans ++;
            return ;
        }
        
        if(x < n) dfs(x + 1, y);// x未到n
        if(y < m) dfs(x, y + 1);// y未到m
    }

}

int main()
{
    cin >> n >> m;
    
    dfs(1, 1);
    
    cout << ans;
    
    return 0;
}

偏移方向:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 40;

int n, m;
int ans;

bool st[N][N];
int dx[2]={0, 1},dy[2]={1, 0};

void dfs(int x, int y)
{
    if(x == n && y == m) ans ++;
    for(int i = 0; i < 2; i ++)
    {
        int a = x + dx[i], b = y + dy[i];
        if(a < 1 || a > n || b < 1 || b > m) continue;
        if(st[a][b]) continue;
        if(a % 2 == 0 && b % 2 == 0) continue;
        
        st[a][b] = true;
        dfs(a, b);
        st[a][b] = false;// 恢复现场
    }

}

int main()
{
    cin >> n >> m;
    
    dfs(1, 1);
    
    cout << ans;
    
    return 0;
}

矩阵

【问题描述】
把 1 ∼ 2020 放在 2 × 1010 的矩阵里。要求同一行中右边的比左边大,列中下边的比上边的大。一共有多少种方案?
答案很大,你只需要给出方案数除以 2020 的余数即可。

思路:动态规划——数字三角形模型的计数问题

f[i][j]:第一行的数的个数为i,第二行的数的个数为j的所有方案的集合

属性:count

初始化

f[0][0] = 1;// 一个都不放

注:要求同一行中右边的比左边大,列中下边的比上边的大

【代码实现】

答案:1340

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2500, mod = 2020;
int f[N][N];


int main()
{
    int n = 1010;
    
    f[0][0] = 1;// 一个都不放
    for(int i = 1; i <= n; i ++)
        for (int j = 0; j <= i; j ++ )// 第一行数字的数目必须比第二行大(下方比上方大)
        {
            f[i][j] = f[i - 1][j] % mod;
            if(j - 1 >= 0)
            f[i][j] = (f[i][j] + f[i][j - 1]) % mod;
        }
    cout << f[n][n];    
    return 0;
}

整数划分

【题目链接】900. 整数划分 - AcWing题库

在这里插入图片描述

样例剖析:

5的7种划分如下:
5
4 1
3 2
3 1 1
2 2 1
2 1 1 1
1 1 1 1 1

思路:

注:未限制选取的个数,可以选择无限次!!!!

把1,2,3, … n分别看做n个物体的体积,这n个物体均无使用次数限制,问恰好能装满总体积为n的背包的总方案数(完全背包问题变形)

在这里插入图片描述

f[i][j]表示前i个整数(1,2…,i)恰好拼成j的方案数

f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2*i] + f[i - 1][j - 3*i].....

f[i][j - i] = f[i - 1][j - i] + f[i - 1][j - 2*i] + f[i - 1][j - 3*i] .....

因此:f[i][j] = f[i - 1][j] + f[i][j - i](类似完全背包的推导!)

即一维:f[j] = f[j] + f[j - i]

初始化:当一个数都不选的时候,只有一种方案

即:for (int i = 0; i <= n; i ++)f[i][0]= 1;
等价变形后: f[0] = 1

【代码实现】

二维:

#include <iostream>

using namespace std;

const int N = 1e3 + 7, mod = 1e9 + 7;

int f[N][N];

int main() 
{
    int n;
    cin >> n;

    for (int i = 0; i <= n; i ++) 
    {
        f[i][0] = 1; // 容量为0时,前 i 个物品全不选也是一种方案
    }

    for (int i = 1; i <= n; i ++) 
    {
        for (int j = 0; j <= n; j ++) 
        {
            f[i][j] = f[i - 1][j] % mod; 
            if (j >= i) f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
        }
    }

    cout << f[n][n] << endl;
}

一维:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 1e9 + 7;
int dp[N];

int main()
{
    int n;
    cin >> n;

    dp[0] = 1;// 容量为0时,前 i 个物品全不选也是一种方案
    for(int i = 1; i <= n; i ++)
        for(int j = i; j <= n; j ++)
            dp[j] = (dp[j] + dp[j - i]) % mod;
    
    cout << dp[n];
    return 0;
}

鸣人的影分身

【题目链接】1050. 鸣人的影分身 - AcWing题库

注:限制选取的个数,不再是完全背包的模型!!!!—— 与整数划分的区别所在(且状态表示也不是前i个数,而是分为了j个数)!!!

思路:

在这里插入图片描述

初始化

f[0][0] = 1;// 一个都不选,其它状态都是不合法的

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 30;
int f[N][N];// f[i][j]:总和为i,且分成j个数的集合
int m, n;

int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        cin >> m >> n;

        memset(f, 0, sizeof f);

        f[0][0] = 1;// 一个都不选
        for(int i = 0; i <= m; i ++)// 鸣人的查克拉可以为0
            for(int j = 1; j <= n; j ++)
            {
                f[i][j] = f[i][j - 1];
                if(i - j >= 0) f[i][j] += f[i - j][j];
            }

        cout << f[m][n] << endl;    
    }
    return 0;
}

DFS组合类型深搜:

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

int m, n;
int ans;

void dfs(int u, int power, int start)// u为第几个盘子,power为当前能量,选的这个数为start(保证升序选择几个苹果)
{
    if(u == n + 1)
    {
        if(power == 0)
        {
            ans ++;
        }
        return ;
    }
    if(start > power) return ;// 可行性剪枝
    for(int i = start; i <= m; i ++)
    {
        dfs(u + 1, power - i, i);
    }
}

int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        cin >> m >> n;
        
        ans = 0;// 多组数据
        dfs(1, m, 0);
        
        cout << ans << endl;
            
    }
    return 0;
}

糖果

【题目链接】1047. 糖果 - AcWing题库

思路:01背包的扩展

在这里插入图片描述

初始化求最值(大)问题,无效方案要初始化为负无穷!!!

    memset(f, -0x3f, sizeof f);// f[i][0], f[0][i]这些都不合法
    f[0][0] = 0;// 注:本题求的是价值而不是方案,所以初始化为0而不是1!!!

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110;
int f[N][N];
// f[i][j]:前i个物品中选,且 糖果总数 % k(余数) 为 j 的所有方案的集合
// 属性 max
// 不选第i个物品:f[i, j] = f[i - 1, j]
// 选!:f[i][j] = f[[i - 1][j - w % k] + ---- 为了防止负数 [j - w % k] ---> [(j - w % k + k) % k]

int get_mod(int a, int b)// a % b的正余数结果
{
    return (a % b + b) % b;
}

int main()
{
    int n, k;
    cin >> n >> k;
    // 求的最大值
    memset(f, -0x3f, sizeof f);// f[i][0], f[0][i]这些都不合法
    f[0][0] = 0;// 注:本题求的是价值而不是方案,所以初始化为0而不是1!!!
    for (int i = 1; i <= n; i ++ )
    {
        int w;
        cin >> w;
        for (int j = 0; j < k; j ++ )// 余数:0~k - 1
            f[i][j] = max(f[i - 1][j], f[i - 1][get_mod(j - w, k)] + w);
    }
    cout << f[n][0];
    return 0;
}

波动数列

【题目链接】1214. 波动数列 - AcWing题库

tips:此题也请与第3题——糖果对比

思路:

在这里插入图片描述

S − ( n − 1 ) d 1 + ( n − 2 ) d 2 + … + d n − 1 n = x \frac {S−(n−1)d1+(n−2)d2+…+dn−1}{n}=x nS(n1)d1+(n2)d2++dn1=x
x为正整数,则说明分子一定是n 的倍数,简单可证 S%n((n−1)d1+(n−2)d2+…+dn−1)%n余数相同时,分子为n的倍数。则最终f[n−1][s%n]的方案数,就是 (n−1)d1+(n−2)d2+…+dn−1n−1d的合法方案数,两者是等价的。

在这里插入图片描述

初始化:求方案数

f[0][0] = 1;// f[i][0], f[0][i]都是不合法的

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010, mod = 100000007;

int f[N][N];

int get_mod(int a, int b)// 正数mod
{
    return (a % b + b) % b;
}

int main()
{
    int n, s, a, b;
    cin >> n >> s >> a >> b;
    
    f[0][0] = 1; // 一个都不选的方案为1,其它都为不合法方案
    for (int i = 1; i < n; i ++ )// n - 1个物品
        for(int j = 0; j < n; j ++)// 枚举余数:0 ~ n - 1
        {
            f[i][j] = (f[i - 1][get_mod(j - a * (n - i), n)] + f[i - 1][get_mod(j + b * (n - i), n)]) % mod;
        }
    cout << f[n - 1][get_mod(s, n)];    
    return 0;
}

砝码称重

【题目链接】3417. 砝码称重 - AcWing题库

在这里插入图片描述

【题目链接】

三种选择方式:

1.不选第i个物品

2.第i个物品放左边(负)

3.第i个物品放右边(正)

状态表示:f[i][j]:从前i个物品中选,且重量为j的所有方案的集合

属性:bool:是否非空

初始化:f[0][0] = true

在这里插入图片描述

【代码实现】

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;


const int N = 110, M = 2e5 + 10, B = M / 2;// 偏移量

int n, m;
int w[N];
bool f[N][M];

int main()
{
    cin >> n;
    
    for (int i = 1; i <= n; i ++ ) 
    {
        cin >> w[i];
        m += w[i]; // 总重量
    }
    
    f[0][0 + B] = true;// 初始化
    for (int i = 1; i <= n; i ++ )
        for(int j = -m; j <= m; j ++)// 枚举重量:范围-m ~ m(-m可能很小,为保证下标不为负数 要加偏移量)
        {
            f[i][j + B] = f[i - 1][j + B];
            if(j + w[i] <= m) f[i][j + B] |= f[i - 1][j + w[i] + B];
            if(j - w[i] >= -m) f[i][j + B] |= f[i - 1][j - w[i] + B];
        }
    int ans = 0;
    for (int j = 1; j <= m; j ++ )
        if(f[n][j + B])
            ans ++;
    cout << ans;        
    return 0;
}

地宝取宫

遇到就会汇总进来~

  1. 状态的含义、表示
  2. 初始化
  3. 枚举顺序等缺一不可!

参考文献:
acwing蓝桥杯辅导课
洛谷题库

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值