状态压缩DP
状态压缩概述
状态压缩就是使用某种方法,简明扼要地以最小代价来表示某种状态,通常是用一串01数字(二进制数)来表示各个点的状态。这就要求使用状态压缩的对象的点的状态必须只有两种,0 或 1;
使用条件
从状态压缩的特点来看,这个算法适用的题目符合以下的条件:
- 解法需要保存一定的状态数据(表示一种状态的一个数据值),每个状态数据通常情况下是可以通过2进制来表示的。这就要求状态数据的每个单元只有两种状态,比如说棋盘上的格子,放棋子或者不放,或者是硬币的正反两面。这样用0或者1来表示状态数据的每个单元,而整个状态数据就是一个由一串0和1组成的二进制数。
- 解法需要将状态数据实现为一个基本数据类型,比如int,long等等,即所谓的状态压缩。状态压缩的目的一方面是缩小了数据存储的空间,另一方面是在状态对比和状态整体处理时能够提高效率。这样就要求状态数据中的单元个数不能太大,比如用int来表示一个状态的时候,状态的单元个数不能超过32(32位的机器),所以题目一般都是至少有一维的数据范围很小。
状态压缩DP
- 状态压缩DP,顾名思义,就是使用状态压缩的动态规划。
- 动态规划问题通常有两种,一种是对递归问题的记忆化求解,另一种是把大问题看作是多阶段的决策求解。这里用的便是后一种,这带来一个需求,即存储之前的状态,再由状态及状态对应的值推演出状态转移方程最终得到最优解。
动态规划多阶段: 一个重要的特性就是无后效性。无后效性就是值对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的发展,而只能通过当前的这个状态。换句话说影响当前阶段状态只可能是前一阶段的状态;那么可以看出如何定义状态是至关重要的,因为状态决定了阶段的划分,阶段的划分保证了无后效性。
位运算
- 有了状态,就需要对状态进行操作或访问
对一个十进制下的信息访问其内部存储的二进制信息,怎么办呢?操作系统是二进制的,编译器中同样存在一种运算符:位运算
-
为了更好的理解状压dp,首先介绍位运算相关的知识。
- 1.’
&
’符号,x & y
,会将两个十进制数在二进制下进行与运算,然后返回其十进制下的值。例如 3(11)&2(10)=2(10)。(注意:括号外是10进制数,括号内是2进制数。下同) - 2.’
|
’符号,x | y
,会将两个十进制数在二进制下进行或运算,然后返回其十进制下的值。例如 3(11)|2(10)=3(11)。 - 3.’
^
’符号,x ^ y
,会将两个十进制数在二进制下进行异或运算,然后返回其十进制下的值。例如 3(11)^2(10)=1(01)。 - 4.’
<<
’符号,x<<2
,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。
’>>
’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。
- 1.’
这四种运算在状态压缩DP中有着广泛的应用,常见的应用如下:
-
1.判断一个数字x二进制下第i位是不是等于1。
- 方法:
if ( ((1<<(i−1)) & x) > 0)
- 证明:将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。
- 方法:
int st = 9; // 1001
int i = 3;
System.out.println((1<<i)); // 1000 8
System.out.println( (st & (1<<i) ) ); // (9 & 8) == 8 ( 1001 & 1000) == 1000
System.out.println( (st & (1<<i)) > 0 ); // true
- 2.将一个数字x二进制下第i位更改成1。
- 方法:
x = x | ( 1<<(i−1) )
- 证明:证明方法与1类似。
- 方法:
- 3.把一个数字二进制下最靠右的第一个1去掉。
- 方法:
x = x & (x−1)
- 方法:
优先级: 位反(~ ) > 算术 > 位左移、位右移 > 关系运算 > 位与 > 位或 > 位异或 > 逻辑运算
状态压缩DP例题练习
import java.io.*;
public class Main {
static int n, k;
static long ans;
//统计x的二进制表示中有多少个1
static int count(int x) {
int cnt = 0;
while (x != 0) {
cnt++;
x &= (x - 1);
}
return cnt;
}
//判断单行状态st是否合法
static boolean check1(int st) {
for (int i = 0; i + 1 < n; i++) {
if ((st & (1 << i)) > 0 && (st & (1 << (i + 1))) > 0) {
return false;
}
}
return true;
}
// 判断当前行和上一行的状态是否合法
static boolean check2(int st, int st2) {
for (int i = 0; i < n; i++) {
if ((st & (1 << i)) > 0) {
if ((st2 & (1 << i)) > 0)
return false;
else if ((st2 & (1 << (i + 1))) > 0)
return false;
else if ((st2 & (1 << (i - 1))) > 0)
return false;
}
}
return true;
}
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] s = in.readLine().split(" ");
n = Integer.parseInt(s[0]);
k = Integer.parseInt(s[1]);
long[][][] f = new long[9][1 << 9][82];
for (int i = 0; i < n; i++) {
for (int st = 0; st < (1 << n); st++) {
if (!check1(st))
continue;
if (i == 0) {
f[i][st][count(st)] = 1;
} else {
for (int j = count(st); j <= k; j++) {
for (int st2 = 0; st2 < (1 << n); st2++) {
if (!check1(st2) || !check2(st, st2)) {
continue;
}
f[i][st][j] += f[i - 1][st2][j - count(st)];
}
}
}
}
}
for (int st = 0; st < (1 << n); st++) {
ans += f[n - 1][st][k];
}
System.out.println(ans);
}
}
import java.util.*;
public class Main {
static int n, k, N = 12, M = 1 << 10, K = 105;
static long[][][] f = new long[N][K][M];
static int[] cnt = new int[M];
static ArrayList<Integer> st = new ArrayList<>();
static ArrayList<ArrayList<Integer>> h = new ArrayList<>();
static int check(int st) {
for (int i = 0; i < n; i++) {
if ((st >> i & 1) != 0 && (st >> i + 1 & 1) != 0)
return 0;
}
return 1;
}
static int count(int st) {
int ans = 0;
for (int i = 0; i < n; i++)
ans += st >> i & 1;
return ans;
}
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
for (int i = 0; i < M; i++) {
h.add(new ArrayList<>());
}
n = reader.nextInt();
k = reader.nextInt();
for (int i = 0; i < 1 << n; i++) {
if (check(i) != 0) {
st.add(i);
cnt[i] = count(i);
}
}
for (int i = 0; i < st.size(); i++) {
for (int j = 0; j < st.size(); j++) {
int a = st.get(i), b = st.get(j);
if ((a & b) == 0 && check(a | b) != 0)
h.get(i).add(j) ;
}
}
f[0][0][0] = 1;
for (int i = 1; i <= n + 1; i++) {
for (int j = 0; j <= k; j++) {
for (int a = 0; a < st.size(); a++) {
for (int b : h.get(a)) {
int c = cnt[st.get(a)];
if (j >= c)
f[i][j][st.get(a)] += f[i - 1][j - c][st.get(b)];
}
}
}
}
System.out.println(f[n + 1][k][0]);
}
}
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] s = in.nextLine().split(" ");
int M = Integer.parseInt(s[0]);
int N = Integer.parseInt(s[1]);
int[] G = new int[M+2];
for (int i = 1; i <= M; i++) {
s = in.nextLine().split(" ");
for (int j = 0; j < N; j++) {
//w[i] += !t * (1 << j);
int t = Integer.parseInt(s[j]) ^ 1;
G[i] += t * (1 << j);
}
}
System.out.println(solve(M,N,G));
}
static List<Integer> state;
static List<List<Integer>> head;
private static int solve(int M,int N,int[] G) {
int[][] dp = new int[14][1<<12];
int mod = (int) 1e8;
state = new ArrayList<>();
head = new ArrayList<>();
for (int i = 0; i < 1 << N; i ++ ) {
if (check(i,N)) {
state.add(i);
}
}
for (int i = 0; i < state.size(); i ++ ) {
List<Integer> ans = new ArrayList<>();
for (int j = 0; j < state.size(); j ++ ) {
int a = state.get(i), b = state.get(j);
//这一行和上一行没有交集,a就可以转移到b
if ( (a & b) == 0) {
ans.add(j);
}
}
head.add(ans);
}
dp[0][0]=1;
for(int i = 1;i <= M+1;i++) {
for(int j=0;j<state.size();j++) {
//G[i]的1不能放,state[j]的i表示要放,所以&一下,结果是1就不合法。
if( (state.get(j) & G[i]) == 0) {
for(int k:head.get(j))
{
dp[i][j]=(dp[i][j]+dp[i-1][k])%mod;
}
}
}
}
return dp[M+1][0];
}
//检查本状态是否能符合题意
static boolean check(int state, int m) {
for(int i = 0; i + 1 < m; i++) {
//不允许存在相邻两个1
if( ( state>>i &1) == 1 && (state>>i+1 &1) == 1) {
return false;
}
}
return true;
}
}
你知道的越多,你不知道的越多。
有道无术,术尚可求,有术无道,止于术。
如有其它问题,欢迎大家留言,我们一起讨论,一起学习,一起进步。