1、计数类DP
900. 整数划分 - AcWing题库
第一种方法
上一篇文章里面的dp题目求的都是最大值或者最小值,但是这一道题求的是个数。根据题意,可以看成1-n个数装满容量为n的背包,而且每个数可以用无限次,所以可以联想到完全背包问题,用完全背包问题的方法来做。
第二种方法
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int N = 1010;
int mod = (int)1e9 + 7;
int[][] f = new int[N][N];
int n = sc.nextInt();
f[0][0] = 1;//注意这里一定要记得初始化
//根据DP思想分析
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= i; j ++){//这里的j一定要记得是小于等于i,因为总数是i,所以j不能超过i
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;
}
System.out.print(res);
}
}
3.状态压缩DP
291. 蒙德里安的梦想 - AcWing题库
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int N = 12, M = 1 << N;//每个格子都有0和1两种状态
long[][] f = new long[N][M];//状态转移方程
int[][] state = new int[M][M];//用来存储相邻列的每对状态是否合法
boolean[] st = new boolean[M];//用来判断这一列的状态是否合法
while(true){
//录入数据 退出循环
int n = sc.nextInt();
int m = sc.nextInt();
if(n == 0 && m == 0) break;
//用来判断出每一列的合法状态
//每一列共有n行,也就是2^n中情况
for(int i = 0; i < 1 << n; i ++){
int cnt = 0;//每循环一种状态一开始前面的0的个数
boolean flag = true;//定义一个布尔值
//这一列的这种状态 从上往下遍历每一个格子
for(int j = 0; j < n; j ++){
//判断这一列的当前这个格子是不是1
//i >> j & 1这个是公式
if((i >> j & 1) == 1){
//如果是1的话 就判断以下这个格子前面的格子(相邻)0的个数是不是偶数
if((cnt & 1) == 1){
flag = false;//如果是奇数的话,则这种状态不合法,退出这层循环
break;
}
cnt = 0;//清空cnt,防止影响到下一次循环
}else cnt ++;//如果现在这位不是1
}
//最后还要判断一下一列中最后一层的0的个数是否为偶数
if((cnt & 1) == 1) flag = false;
//把这种状态合法与否都存进st数组中,方便下一个预处理
st[i] = flag;
}
//开始判断相邻列的哪对状态合法
for(int i = 0; i < 1 << n; i ++){//这是第i - 1列
Arrays.fill(state[i], 0);//清空状态
for(int j = 0; j < 1 << n; j ++){//这是第i列
if((i & j) == 0 && st[i | j]){//相邻列合法的条件
//表示i状态的前一列和j状态的这一列是合法的
state[i][j] = 1;
}
}
}
for(int i = 0; i < N; i ++){
Arrays.fill(f[i], 0);
}
f[0][0] = 1;//什么都没有的时候,空着表示一种方案
//开始dp,暴力枚举所有的方案
//横着来进行
for(int i = 1; i <= m; i ++){
for(int j = 0; j < 1 << n; j ++){
for(int k = 0; k < 1 << n; k ++){
//把所有的方案数加起来
if(state[j][k] == 1) f[i][j] += f[i - 1][k];
}
}
}
System.out.println(f[m][0]);
}
}
}
91. 最短Hamilton路径 - AcWing题库
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
int N = 20, M = 1 << N;
int INF = 0x3f3f3f3f;
int n = sc.nextInt();
int[][] w = new int[N][N];//用来存储点与点之间的距离
int[][] f = new int[M][N];//状态转移方程
for(int i = 0; i < n; i ++){
for(int j = 0; j < n; j ++){
w[i][j] = sc.nextInt();
}
}
for(int i = 0; i < 1 << n; i ++){
Arrays.fill(f[i], INF);
}
f[1][0] = 0;//从起点0走到0,只经过了0这一个点
for(int i = 0; i < 1 << n; i ++){//有这么多种状态路径
for(int j = 0; j < n; j ++){
if((i >> j & 1) == 1){
for(int k = 0; k < n; k ++){//点与点之间不要求顺序
if((i - (1 << j) >> k & 1) == 1){
f[i][j] = Math.min(f[i][j], f[i - (1 << j)][k] + w[k][j]);
}
}
}
}
}
System.out.print(f[(1 << n) - 1][n - 1]);//在所有路径里面走到这个n-1点的最小值
}
}
4.树形DP
285. 没有上司的舞会 - AcWing题库
import java.util.*;
public class Main{
static int N = 6010, n, idx;
static int[] happy = new int[N];
static int[][] f = new int[N][2];
static boolean[] has_father = new boolean[N];
static int[] h = new int[N], ne = new int[N], e = new int[N];
public static void add(int a, int b){
e[idx] = b;
ne[idx] = h[a];
h[a] = idx ++;
}
public static void dfs(int u){
f[u][1] = happy[u];//一开始的时候给它一个快乐值
for(int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
dfs(j);
f[u][1] += f[j][0];
f[u][0] += Math.max(f[j][0], f[j][1]);
}
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
for(int i = 1; i <= n; i ++){
happy[i] = sc.nextInt();
}
Arrays.fill(h, -1);//将头节点置为-1
for(int i = 0; i < n - 1; i ++){
int a = sc.nextInt();
int b = sc.nextInt();
has_father[a] = true;//用于判断谁没有父节点,也就是谁是根节点
add(b, a);//结成树
}
int root = 1;
while(has_father[root]) root ++;//找到根节点
dfs(root);
System.out.print(Math.max(f[root][0], f[root][1]));
}
}
5.记忆化搜索
901. 滑雪 - AcWing题库
import java.util.*;
public class Main{
static int N = 310, n, m;
static int[][] h = new int[N][N];
static int[][] f = new int[N][N];//从哪个点开始滑
public static int df(int x, int y){
int[] dx = {-1, 0, 1, 0};//四个方向,偏移量法
int[] dy = {0, 1, 0, -1};
if(f[x][y] != -1) return f[x][y];//如果已经遍历过,就直接返回
f[x][y] = 1;//无论在哪,至少都有一步
for(int i = 0; i < 4; i ++){
int a = x + dx[i];
int b = y + dy[i];
if(a >= 1 && b >= 1 && a <= n && b <= m && h[x][y] > h[a][b]){//不越界,下一步比这一步低
f[x][y] = Math.max(f[x][y], df(a, b) + 1);
}
}
return f[x][y];
}
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
h[i][j] = sc.nextInt();//输入数据
}
}
for(int i = 0; i < N; i ++){
Arrays.fill(f[i], -1);
}
int res = 0;
for(int i = 1; i <= n; i ++){
for(int j = 1; j <= m; j ++){
res = Math.max(res, df(i, j));//遍历每一个位置,找到最大的
}
}
System.out.print(res);
}
}