• [计数dp] 整数划分(模板题+计数dp+完全背包变种题)


    计数类 dp 可分为 计数 dp 和数位统计 dp。大多是用来统计方案数什么的,特别强调 不重不漏,在此还是根据各个题的特点将计数 dp 和数位 dp 分开整理。其实数位 dp 的题目会相对多很多…

    计数dp 模板题

    AcWing 900.整数划分

    重点: 计数 dp、完全背包问题抽象

    首先模拟下样例便于理解本题:

    5 = 5
      = 4 + 1
      = 3 + 2
      = 3 + 1 + 1
      = 2 + 1 + 1 + 1
      = 2 + 2 + 1
      = 1 + 1 + 1 + 1 + 1
    共七种划分方式
    

    故我们可以将问题抽象为一个容量为 n 的背包,有 n 个体积为 1 ~ n 的物品,求恰好将该背包的方案数。每种物品可以使用无限次,故该问题是一个完全背包问题。

    思路:

    • 状态定义:
      • f[i][j]:从 1~i 中选,且总体积恰好为 j 的选法数量
    • 状态转移:
      • 分类依据:根据最后一个物品选择个数进行状态划分,和完全背包问题的状态划分一致。
        • 第 i 个物品选 0 个:f[i-1][j]
        • 第 i 个物品选 1 个:f[i-1][j - i]
        • 第 i 个物品选 2 个:f[i-1][j - 2*i]
        • 第 i 个物品选 s 个:f[i-1][j - s*i]
      • 至此,朴素版完全背包问题就到此为止。但是,完全背包问题有一个非常厉害的优化方式。建议阅读:[背包] 背包问题算法模板(模板)
        • f[i][j] = f[i-1][j]+f[i-1][j-1]+f[i-1][j-i*2]+...+f[i-1][j-i*s]
        • f[i][j-i] = f[i-1][j-i]+f[i-1][j-i*2] +..+ f[i-1][j-i*s]
        • 仔细对比,发现 f[i][j-i] 和 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]=f[j]+f[j-i]。体积从小到大循环即可
    • 状态初始化:f[0]=1,一个数都不选的方案是 1

    完全背包代码:

    const int mod = 1e9 + 7;
    void solve() {
    	int n;
    	cin >> n;
    	int f[n + 1] = {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;
    }
    

    除了完全背包的写法及状态定义外,也有一种其它的状态定义方式,状态转移方程不同但是却能得到相同的结果…

    思路:

    • 状态定义:
      • f[i][j] :所有总和是 i,并且恰好表示成 j 个数的和的方案的数量
    • 状态转移:
      • 分类依据:根据表示成的这 j 个数中是否包含 1,来进行集合划分
        • 如果包含 1,等价于 f[i-1][j-1],等价于和是 i-1 数量是 j-1 的选法数量
        • 如果每个数大于 1,则等价于将这 j 个数全部减去一个 1,则总数减去了 j,其和 f[i-j][j] 方案数相等。
      • 故状态转移方程 f[i][j] = f[i-1][j-1]+f[i-j][j]
      • 答案即为 ans = f[n][1] + f[n][2] +...+f[n][n]
    • 状态初始化f[0][0] = 1 代表总和是 0 的时候选 0 个的方案数是 1

    代码:

    const int mod = 1e9 + 7;
    const int N = 1e3 + 10;
    int f[N][N];
    void solve() {
    	int n;
    	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 ans = 0;
    	for (int i = 1; i <= n; ++i)ans = (ans + f[n][i]) % mod;
    	cout << ans << endl;
    }
    

    故可看出,同一个 dp 问题,不同的思考方式,不同的集合划分,不同的状态转移方程,只有思路是正确的,那么就是可行的。当然,在本题,划分方式不同导致了状态转移方程的不同,进而导致了求解答案时也不同。

    参考

    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    webservice
    AppDomain (转)
    Apache和Nginx防盗链的几种配置方法
    优化PHP代码的40条建议
    file_get_contents无法请求https连接的解决方法
    PHP SPL
    Ubuntu 查看系统信息
    PHP导出Excel
    mysql集群
    配置yum源的两种方法
  • 原文地址:https://www.cnblogs.com/RioTian/p/14769640.html
Copyright © 2020-2023  润新知