当前位置: 首页 > article >正文

数据结构与算法学习笔记----计数类DP

数据结构与算法学习笔记----计数类DP

@@ author: 明月清了个风
@@ first publish time: 2025.2.16

ps⭐️计数类DP主要解决计数问题,这里给出了一题经典模型,提供了两种解题思路,并且给出了代码的优化过程——一种将这题看成完全背包问题,另一种从题目本身出发。

Acwing 900. 整数划分

[原题链接](900. 整数划分 - AcWing题库)

一个正整数 n n n可以表示成若干个正整数之和,形如: n = n 1 + n 2 + ⋯ + n k n = n_1 + n_2 + \cdots + n_k n=n1+n2++nk,其中 n 1 ≥ n 2 ≥ ⋯ ≥ n k , k ≥ 1 n_1 \ge n_2 \ge \cdots \ge n_k, k \ge 1 n1n2nk,k1

我们将这样的一种表示成为正整数 n n n的一种划分。

现在给定一个正整数 n n n,请你求出 n n n共有多少种不同的划分方法。

输入格式

共一行,包含一个整数 n n n

输出格式

共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对 1 0 9 + 7 10^9 + 7 109+7取模。

数据范围

1 ≤ n ≤ 1000 1 \le n \le 1000 1n1000

第一种思路

要求将整数 n n n分为若干个整数,也就是使用若干个整数凑出整数 n n n,并且对于整数没有其他要求,因此可以看成是完全背包问题,也就是有一个容积为 n n n的背包,要求使用正整数将其填满,并且每个正整数是无限个,唯一的区别是这里我们要正好凑出容积 n n n并且要求的属性也从最大值变成了方案数。

对于状态表示,使用f[i][j],表示的是从 1 ∼ i 1 \sim i 1i中的正整数选,总体积恰好为 j j j的方案,要求是这个集合中的方案数量

对于状态转移,同样根据最后一个数的选择个数——第 i i i个物品选择 0 , 1 , 2 , ⋯   , k 0,1,2,\cdots,k 0,1,2,,k个进行划分,和完全背包问题完全一致,比如选择 2 2 2个第 i i i个物品,那么这一项就是f[i - 1][j - 2 * i]

那么这样的时间复杂度就是二维状态n^2乘上状态转移计算量 ln ⁡ n \ln n lnn(这里是一个调和级数,可以看成是 log ⁡ n \log n logn)。

当然也可以像完全背包问题那样进行优化:

首先看f[i][j]由哪些状态计算得出:
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − i ] + f [ i − 1 ] [ j − 2 ∗ i ] + ⋯ + f [ i − 1 ] [ j − k ∗ i ] f[i][j] = f[i -1][j] + f[i - 1][j - i] + f[i - 1][j - 2 * i] + \cdots + f[i - 1][j - k * i] f[i][j]=f[i1][j]+f[i1][ji]+f[i1][j2i]++f[i1][jki]
然后来看另一项f[i][j - i]如何转移
f [ i ] [ j ] = f [ i − 1 ] [ j − i ] + f [ i − 1 ] [ j − 2 ∗ i ] + ⋯ + f [ i − 1 ] [ j − k ∗ i ] f[i][j] = f[i -1][j - i] + f[i - 1][j - 2 * i] + \cdots + f[i - 1][j - k * i] f[i][j]=f[i1][ji]+f[i1][j2i]++f[i1][jki]
可以发现就是f[i][j]的转移方程除了第一项之外的后面部分,因此就可以得到新的状态转移方程
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − i ] f[i][j] = f[i - 1][j] + f[i][j - i] f[i][j]=f[i1][j]+f[i][ji]
这样就可以将代码中的第三重循环优化掉了。

进一步的,还可以使用一维数组优化, 降低空间复杂度,具体的看代码吧,然后可以复习一下背包问题。

还有一点注意的就是初始化,每次都会提到,这里f[0](也就是二维的f[0~i][0])应该初始化为 1 1 1,因为从前 0 ∼ i 0 \sim i 0i个数中选出体积为 0 0 0的方案就只有一种——什么都不选。

代码

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

using namespace std;

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

int n;
int f[N];

int main()
{
    cin >> n;
    
    f[0] = 1;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = i; j <= n; j ++)
            f[j] = (f[j] + f[j - i]) % mod; 
    }
    
    cout << f[n] << endl;
    
    return 0;
    
}

第二种思路

这里可以使用另一种状态表示方法,使用f[i][j],表示的是总和是 i i i,并且恰好由 j j j个数表示。

这里的状态划分比较难,可以背下来,划分为两类,分别是表示方法中最小值是 1 1 1和最小值不是 1 1 1

其中,最小值是 1 1 1的表示方法,可以由f[i - 1][j - 1]转移而来,也就是总和中减去了这个 1 1 1,并且表示方法中整数个数也减去 1 1 1个数。

另一类比较抽象,因为最小值大于 1 1 1,无法像第一类一样直接转移,这里我们可以将集合中的 j j j个数每个数都减去 1 1 1,那么总和会减去 j j j,状态表示为f[i - j][j],可以通过将这个状态中的 j j j个数都加上 1 1 1变回去,因此两种状态是等价的,可以这样转移

所以最终的状态转移方程就是 f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + f [ i − j ] [ j ] f[i][j] = f[i - 1][j - 1] + f[i - j][j] f[i][j]=f[i1][j1]+f[ij][j],但是这里最后的答案需要遍历一遍,也就是 f [ n ] [ 1 ] + f [ n ] [ 2 ] + ⋯ + f [ n ] [ n ] f[n][1] + f[n][2] + \cdots + f[n][n] f[n][1]+f[n][2]++f[n][n]

代码

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

using namespace std;

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

int n;
int f[N][N];

int main()
{
    cin >> n;
    
    f[0][0] = 1;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= i; j ++)
            f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
    }
    
    int res = 0;
    for(int i = 1; i <= n; i ++) res = (res + f[n][i]) % mod;
    
    
    cout << res << endl;
    
    return 0;
    
}

http://www.kler.cn/a/553031.html

相关文章:

  • 学习路之微服务--PHP中实现微服务几种方式
  • vue非组件的初学笔记
  • unity学习49:寻路网格链接 offMeshLinks, 以及传送门效果
  • Java基础——代理模式
  • Vue的双向数据绑定和React的单向数据流在处理对象数组时的行为
  • html css js网页制作成品——HTML+CSS李钧锐网页设计(4页)附源码
  • HCIA-Access V2.5_13_1_4_VLAN切换
  • C语言中ASCII码与整型互相转换的那些事儿
  • 前端VUE3的面试题
  • 深入解析SORT多目标跟踪算法:从原理到实现
  • 工业制造能耗管理新突破,漫途MTIC-ECM平台助力企业绿色转型!
  • 亲测Windows部署Ollama+WebUI可视化
  • 武汉小米 Java 岗位一二面校招面经
  • ARMS 助力假面科技研发运维提效,保障极致游戏体验
  • PostgreSQL 的崛起与无服务器数据库的新时代
  • hot100-3、438、560、239、240、160、234(2简3中1难)
  • 网络原理-
  • Miniconda + VSCode 的Python环境搭建
  • 【架构思维基础:如何科学定义问题】
  • 青少年编程与数学 02-009 Django 5 Web 编程 23课题、安全性