第一章动态规划(九)

状态压缩DP

棋盘类(基于连通性)

例题:1064.骑士
在这里插入图片描述
————————————————————————————————————————————————————
在这里插入图片描述

import java.util.ArrayList;
import java.util.Scanner;

public class Main {
	static int N = 55;
	static int M = 1 << 10;
	static int K = 110;
	static int n;
	static int m;
	static int[] cnt = new int[M];
	static ArrayList<Integer>[] head = new ArrayList[M]; 
	static ArrayList<Integer> state = new ArrayList<>();
	static long[][][] f = new long[N][K][M];

	public static void main(String[] args){
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		m = sc.nextInt();
		sc.close();
		
		for (int i = 0; i < head.length; i++) {
			head[i] = new ArrayList<>();
		}
		for(int i = 0;i < 1 << n;i++) {
			if(check(i)) {
				state.add(i);
				cnt[i] = count(i);
			}
		}
		for(int i = 0;i < state.size();i++) {
			for (int j = 0; j < state.size(); j++) {
				int a = state.get(i);
				int b = state.get(j);
				if((a & b) == 0 && check(a | b)) {
					head[i].add(j);
				}
			}
		}
		f[0][0][0] = 1;
		for (int i = 1; i <= n + 1; i++) {
			for (int j = 0; j <= m; j++) {
				for (int a = 0; a < state.size(); a++) {
					for(int b : head[a]) {
						int c = cnt[state.get(a)];
						if(j >= c) {
							f[i][j][a] += f[i - 1][j - c][b];
						}
					}
				}
			}
		}
		System.out.println(f[n + 1][m][0]);
	}

	private static int count(int state) {//返回1的个数
		int res = 0;
		for (int i = 0; i < n; i++) {
			res += (state >> i) & 1;
		}
		return res;
	}

	private static boolean check(int state) {//状态是否合法
		for(int i = 0;i < n;i++) {
			if(((state >> i) & 1) == 1 && ((state >> i + 1) & 1) == 1)
				return false;
		}
		return true;
	}
}

例题:327. 玉米田
在这里插入图片描述
摘自"小呆呆"
在这里插入图片描述
注意:题目中还有着哪些坑不能种玉米,需要特殊标记一下,w[i]记录的是第 i 行能不能种玉米的状态,w[i] 的二进制表示中,某位是1表示该位置不能填坑,0表示该位置可以填坑,当枚举到当前状态state时,若state & w[i] == 0表示合法
时间复杂度:
O(n∗s∗k),n表示行数,s表示合法状态数量,k表示合法状态的转移数量

import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    static int N = 14,M = 1 << N ;
    static int[] w = new int[N];
    static int n,m;
    static ArrayList<Integer> state = new ArrayList<Integer>();
    static ArrayList<Integer>[] head = new ArrayList[M];
    static int[][] f = new int[N][M];
    static int mod = 100000000;
    
    private static boolean check(int s) {
        for(int i = 0;i < m;i ++) {
            if((s >> i & 1) == 1 && (s >> i + 1 & 1) == 1)
                return false;
        }
        return true;
    }
    
    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 ++) {
            int t = 0;
            for(int j = 0;j < m;j ++) {
                int x = sc.nextInt();
                if(x == 0) t += 1 << j;
            }
            w[i] = t;
        }
        sc.close();

        //预处理出相邻的不能为1的状态
        for(int i = 0;i < 1 << m;i ++) {
            if(check(i))
                state.add(i);
        }

        //判断哪些状态和哪些状态可以搭配
        for(int i = 0;i < state.size();i ++) {
            head[i] = new ArrayList<Integer>();
            for(int j = 0;j < state.size();j ++) {
                int a = state.get(i);
                int b = state.get(j);
                if((a & b) == 0)
                    head[i].add(j);
            }
        }

        f[0][0] = 1;
        for(int i = 1;i <= n;i ++) {
            for(int a = 0;a < state.size();a ++) {
                if((state.get(a) & w[i]) == 0) {
                    for(int b : head[a]) {
                        f[i][a] = (f[i][a] + f[i - 1][b]) % mod;
                    }
                }
            }
        }
        long res = 0;
        for(int i = 0;i < 1 << m;i ++) res = (res + f[n][i]) % mod;
        System.out.println(res);
    }
}

例题:292. 炮兵阵地
在这里插入图片描述
摘自"小呆呆"
在这里插入图片描述
注意:题目中还有 ‘H’ 位置不能放大炮,需要特殊标记一下,w[i]记录的是第 i 行能不能放大炮的状态,w[i]的二进制表示中,某位是1表示该位置不能放大炮,0表示该位置可以放大炮,当枚举到当前状态state时,若state & w[i] == 0表示合法
时间复杂度
上限复杂度是O(n∗s2∗k),n 表示行数,s 表示合法状态数量,k 表示合法状态的转移数量

import java.util.ArrayList;
import java.util.Scanner;

public class Main {
    static int N = 110,M = 1 << 10;
    static int[] w = new int[N];
    static int n , m;
    static ArrayList<Integer> state = new ArrayList<Integer>();
    static int[] cnt = new int[M];
    static int[][][] f = new int[2][M][M];
    
    private static boolean check(int state) {
        for(int i = 0;i < m;i ++) {
            if((state >> i & 1) == 1 && ((state >> i + 1 & 1) == 1 || (state >> i + 2 & 1) == 1)) return false ;
        }
        return true;
    }
    
    private static int count(int s) {
        int res = 0;
        for(int i = 0;i < m;i ++)
            if((s >> i & 1) == 1)
                res ++;
        return res;
    }
    
    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 ++) {
            char[] g = sc.next().toCharArray();
            int t = 0;
            //不可填表示1,可填表示0
            for(int j = 0;j < m;j ++) {
                if(g[j] == 'H') t += (1 << j);
            }
            w[i] = t;
        }
        sc.close();
        
        //预处理出相邻不为1的状态
        for(int i = 0; i < 1 << m;i ++) {
            if(check(i)) {
                state.add(i);
                cnt[i] = count(i);
            }
        }

        for(int i = 1;i <= n;i ++)
            for(int j = 0;j < state.size();j ++)//当前行
                for(int k = 0;k < state.size();k ++)//倒数第二行
                    for(int u = 0;u < state.size();u ++) {
                        int a = state.get(j);
                        int b = state.get(k);
                        int c = state.get(u);
                        if((a & b) != 0 || (a & c) != 0 || (b & c) != 0) continue;
                        if((w[i] & a) != 0 || (w[i - 1] & b) != 0) continue;
                        f[i & 1][j][k] = Math.max(f[i & 1][j][k], f[i - 1 & 1][k][u] + cnt[a]);
                    }

        int res = 0;
        for(int i = 0;i < state.size();i ++)
            for(int j = 0;j < state.size();j ++)
                res = Math.max(res,f[n & 1][i][j]);
        System.out.println(res);
    }
}

例题:524. 愤怒的小鸟
在这里插入图片描述
摘自y总和小呆呆题解
在这里插入图片描述
在这里插入图片描述
方法1:记忆化搜索

f[state] = dfs(state)表示从该状态覆盖所有的点需要多少条抛物线

int dfs(int state) {// state表示当前状态哪些点被覆盖
    if(state 已经覆盖所有的点) return 0
    if(state 状态已经搜索过) retrun f[state]的值

    任选出没有被覆盖的点x
    枚举所有与x号点相关的抛物线
        当该抛物线不能覆盖x号点 即path[x][i] == 0 continue;
        f[state] = Math.min(f[state],dfs(state | path[x][i]) + 1);

    返回 f[state];
}

方法2:线性DP

  • 切入点:i | path[x][j] 是大于等于 i,i 才能从小到大枚举,从 i 转移到 i | path[x][j]
  • f[state] : 表示从该状态覆盖所有的点需要多少条抛物线
  • 从 0 枚举到 (1 << n) - 1,找到任意一个未覆盖的点,枚举所有与 x 号点相关的抛物线,最终返回 f[(1 << n) - 1]

【记忆化搜索】

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int n,m;
    static int N = 18,M = 1 << 18;
    static int[][] path = new int[N][N];
    static Pair[] pair = new Pair[N];
    static double esp = 1e-8;
    static int[] f = new int[M];//表示该状态需要抛物线的数量
    
    private static int cmp(double a,double b) {
        if(Math.abs(a - b) < esp) return 0;
        if(a > b) return 1;
        return -1;
    }
    private static int dfs(int state) {
        if(state == (1 << n) - 1) return 0;
        if(f[state] != -1) return f[state];

        //任选出没有被覆盖的点x
        int x = 0;
        for(int i = 0;i < n;i ++) {
            if((state >> i & 1) == 0) {
                x = i;
                break;
            }
        }
        int res = 0x3f3f3f3f;
        //枚举所有与x号点相关的抛物线
        for(int i = 0;i < n;i ++) {
            if (path[x][i] == 0) continue;//当该抛物线不能覆盖x号点
            res = Math.min(res,dfs(state | path[x][i]) + 1);
        }
        return f[state] = res;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        while(T -- > 0) {
            n = sc.nextInt();
            m = sc.nextInt();
            for(int i = 0;i < n;i ++) {
                double x = sc.nextDouble();
                double y = sc.nextDouble();
                pair[i] = new Pair(x,y);
            }

            for(int i = 0;i < n;i ++) Arrays.fill(path[i], 0);

            //找出所有抛物线对应点的状态
            for(int i = 0;i < n;i ++) {
                path[i][i] = 1 << i;
                for(int j = 0;j < n;j ++) {
                    double x1 = pair[i].x, y1 = pair[i].y;
                    double x2 = pair[j].x, y2 = pair[j].y;

                    if(cmp(x1,x2) == 0) continue;
                    double a = (y1 / x1 - y2 / x2) / (x1 - x2);
                    double b = y1 / x1 - a * x1;

                    if(cmp(a,0) >= 0) continue;
                    int state = 0;
                    //判断哪些点在该抛物线上
                    for(int k = 0;k < n;k ++) {
                        double x = pair[k].x;
                        double y = pair[k].y;
                        if(cmp(a * x * x + b * x,y) == 0) state += 1 << k;
                    }
                    path[i][j] = state;
                }
            }
            Arrays.fill(f, -1);
            System.out.println(dfs(0));
        }
        sc.close();
    }
}
class Pair {
    double x,y;
    Pair(double x,double y) {
        this.x = x;
        this.y = y;
    }
}

【线性DP】

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    static int n,m;
    static int N = 18,M = 1 << 18;
    static int[][] path = new int[N][N];
    static Pair[] pair = new Pair[N];
    static double esp = 1e-8;
    static int[] f = new int[M];//表示从该状态覆盖所有的点需要多少条抛物线
    
    private static int cmp(double a,double b) {
        if(Math.abs(a - b) < esp) return 0;
        if(a > b) return 1;
        return -1;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int T = sc.nextInt();
        while(T -- > 0) {
            n = sc.nextInt();
            m = sc.nextInt();
            for(int i = 0;i < n;i ++) {
                double x = sc.nextDouble();
                double y = sc.nextDouble();
                pair[i] = new Pair(x,y);
            }

            for(int i = 0;i < n;i ++) Arrays.fill(path[i], 0);

            //找出所有抛物线对应点的状态
            for(int i = 0;i < n;i ++) {
                path[i][i] = 1 << i;
                for(int j = 0;j < n;j ++) {
                    double x1 = pair[i].x, y1 = pair[i].y;
                    double x2 = pair[j].x, y2 = pair[j].y;

                    if(cmp(x1,x2) == 0) continue;
                    double a = (y1 / x1 - y2 / x2) / (x1 - x2);
                    double b = y1 / x1 - a * x1;

                    if(cmp(a,0) >= 0) continue;
                    int state = 0;
                    //判断哪些点在该抛物线上
                    for(int k = 0;k < n;k ++) {
                        double x = pair[k].x;
                        double y = pair[k].y;
                        if(cmp(a * x * x + b * x,y) == 0) state += 1 << k;
                    }
                    path[i][j] = state;
                }
            }
            Arrays.fill(f, 0x3f3f3f3f);
            f[0] = 0;
            //注意:i | path[x][j] 是大于等于i,i才能 从小到大枚举,从i转移到i | path[x][j]
            for(int i = 0;i < 1 << n;i ++) {
                //找到任意一个未覆盖的点
                int x = 0;
                for(int j = 0;j < n;j ++) {
                    if((i >> j & 1) == 0) {
                        x = j;
                        break;
                    }
                }
                //枚举所有与x号点相关的抛物线
                for(int j = 0;j < n;j ++)
                    f[i | path[x][j]] = Math.min(f[i | path[x][j]], f[i] + 1);

            }
            System.out.println(f[(1 << n) - 1]);
        }
        sc.close();
    }
}
class Pair {
    double x,y;
    Pair(double x,double y) {
        this.x = x;
        this.y = y;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值