2024-02-07(计数类DP、状态压缩DP、树形 DP、记忆化搜索)

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);
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值