计数类DP - 整数划分

149 阅读2分钟
  • 问题背景

Snipaste_2023-03-27_18-12-29.png

  • 解法一

  • Dp分析

Snipaste_2023-03-27_18-16-57.png

  • 状态表示

    • 二维状态表示f(i,j)
    • f(i,j)表示的是哪一个集合:所有满足如下条件的集合
      • 将其看做成完全背包问题,物品就是从1n,每个数可以选无限次
      • 从前i个数中选,它们和恰好是j的选法
    • f(i,j)存的是什么属性:MaxMin数量;在这里f(i,j)存的应该是数量,即从前i个数中选,它们和恰好是j的选法数量
  • 状态计算:f(i,j)可以怎么算出来?

    • 这里就类似完全背包问题,将集合分成k+1个子集
      • 每次选择k个第i个数,k0取到 k * i <= j
      • f(i, j) += f(i - 1, j - k * i)
  • 代码

    • //初始化
      //只选1,总和为i的选法数量为1
      //从前i个数中选,总和为0的选法数量为1,即什么都不选
      for (int i = 1; i <= n; i++) {
          f[1][i] = 1;
          f[i][0] = 1;
      }
      for (int i = 1; i <= n; i++) {
          for (int j = 1; j <= n; j++) {
              for (int k = 0; k * i <= j; k++) {
                  f[i][j] += f[i - 1][j - k * i];
              }
          }
      }
      
  • 优化

    • 观察状态计算的过程,可以发现

      • 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),与k无关
    • 因此可以做如下优化

      • k层循环删掉
    • 代码

      • //初始化
        //只选1,总和为i的选法数量为1
        //从前i个数中选,总和为0的选法数量为1,即什么都不选
        for (int i = 1; i <= n; i++) {
            f[1][i] = 1;
            f[i][0] = 1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                f[i][j] = f[i - 1][j];
                if (j >= i) {
                    f[i][j] += f[i][j - i];
                }
            }
        }
        
  • 再优化

    • 观察状态计算的过程,可以发现两个特点

      • f(i, j) 首先会继承 f(i - 1, j) 的值
      • 然后再加上当前第i层在它之前就更新完了的 f(i, j - i) 的值
    • 因此可以做如下优化

      • i 这一维直接优化掉,i 的每一层循环中的 j 都在原先的 j 上进行覆盖
      • j 层循环直接从 i 开始
    • 代码

      • //初始化
        f[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = i; j <= n; j++) {
                    f[j] += f[j - i];
            }
        }
        
  • 解法二

  • Dp分析

Snipaste_2023-03-27_19-28-04.png

  • 状态表示

    • 二维状态表示f(i,j)
    • f(i,j)表示的是哪一个集合:所有满足如下条件的集合
      • 所有总和为i,并且恰好由j个数组成的选法
    • f(i,j)存的是什么属性:MaxMin数量;在这里f(i,j)存的应该是数量,即所有总和为i,并且恰好由j个数组成的选法数量
  • 状态计算:f(i,j)可以怎么算出来?

    • 将集合分为两个子集
      • 选择的数中最小值为1
        • f(i - 1, j - 1)
      • 选择的数中最小值大于1
        • f(i - j, j)
        • j1取到i - 1,但是还是写成j <= i,避免i=1j取不到数
    • 因此 f(i, j) = f(i - 1, j - 1) + f(i - j, j)
  • 代码

    • //总和为0,并且恰好由0个数组成的选法为1
      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];
          }
      }
      int ans = 0;
      for (int i = 1; i <= n; i++) {
          ans += f[n][i];
      }
      

练习

01 整数划分

  • 题目

Snipaste_2023-03-27_20-10-02.png

  • 思路一题解一
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static final int mod = (int) 1e9 + 7;
    //即从前i个数中选,它们和恰好是j的选法 的数量
    public static int[][] f = new int[N][N];
    public static int n;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        n = Integer.parseInt(br.readLine());

        //初始化
        //只选1,总和为i的选法数量为1
        //从前i个数中选,总和为0的选法数量为1,即什么都不选
        for (int i = 1; i <= n; i++) {
            f[1][i] = 1;
            f[i][0] = 1;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                for (int k = 0; k * i <= j; k++) {
                    f[i][j] = (f[i][j] + f[i - 1][j - k * i]) % mod;
                }
            }
        }
        pw.println(f[n][n]);
        br.close();
        pw.close();
    }
}
  • 思路一题解二
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static final int mod = (int) 1e9 + 7;
    //即从前i个数中选,它们和恰好是j的选法 的数量
    public static int[][] f = new int[N][N];
    public static int n;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        n = Integer.parseInt(br.readLine());

        //初始化
        //只选1,总和为i的选法数量为1
        //从前i个数中选,总和为0的选法数量为1,即什么都不选
        for (int i = 1; i <= n; i++) {
            f[1][i] = 1;
            f[i][0] = 1;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                f[i][j] = f[i - 1][j];
                if (j >= i) {
                    f[i][j] = (f[i][j] + f[i][j - i]) % mod;
                }
            }
        }
        pw.println(f[n][n]);
        br.close();
        pw.close();
    }
}
  • 思路一题解三
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static final int mod = (int) 1e9 + 7;
    //即从前i个数中选,它们和恰好是j的选法 的数量
    public static int[] f = new int[N];
    public static int n;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        n = Integer.parseInt(br.readLine());

        //初始化
        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;
            }
        }
        pw.println(f[n]);
        br.close();
        pw.close();
    }
}
  • 思路二题解四
import java.io.*;
import java.util.*;

public class Main {
    public static final int N = 1010;
    public static final int mod = (int) 1e9 + 7;
    //所有总和为i 并且恰好由j个数组成的选法 的数量
    public static int[][] f = new int[N][N];
    public static int n;

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
        n = Integer.parseInt(br.readLine());

        //总和为0,并且恰好由0个数组成的选法为1
        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;
        }
        pw.println(ans);
        br.close();
        pw.close();
    }
}