状压 DP 基础NO.1

状压 DP 简介
状压 dp 是动态规划的一种,通过将状态压缩为整数来达到优化转移的目的。

需要有位运算的基础

原博客:
https://www.cnblogs.com/ljy-endl/p/11627018.html

位运算的注释与代码如下

#include<iostream>

using namespace std;

int main(){
    
    
	
	int x = 0b1001;
	//	x = x >> 2; 
	// >>> x*4;
	// x = x << 3;
	// >>> x//8; 
	
	//	第二个 是不是 1: 
	int i = 2;
	if( ( (1<< (i-1)) &x) > 0) {
    
    
		cout << 1 << endl;
	}else cout << 0 << endl;
	
	//0b10110 & 00100 >>> 100 > 0;
	//0b10010 & 00100 >>> 0;
	// & : 都为1 才是1 
	
	x = 0b1001;
	//将一个数字x二进制下第i位更改成1
	i = 2;
	x = (1 << (i-1)) | x;
	cout << x << endl;
	
	//0b10110 | 00100 >>> 10110;
	//0b10010 | 00100 >>> 10110;
	// | :有零 补为 1 
	
	x = 0b1001;
	//将一个数字x二进制下第i位更改成0
	i = 4;
	x = x & ~(1 << (i-1));
	cout << x << endl;
	
	//0b10110 & 11011 >>> 10010;
	//0b10111 & 11111 >>> 10111;
	//只有原来是1的 补反码 才是1,那么把要变的地方 换成0;
	
	x = 0b1010;
	// x = 10;
	//把一个数字二进制下最靠右的第一个1去掉。
	x = x & (x-1);

	// x-1 = 1001;
	//减去1 后  最靠右的第一个1 与 原来的错位 ,再与运算后 变成了0; 
	// 1001  & 1010 = 1000; 
	cout << x << endl;
	
} 

**

原话

**

实际状压dp顾名思义,就是采用位运算,来记录更多的必须记录的状态来做dp有了比较深的dp功底后只要对位运算有了解就可以解决问题。。。
考虑到每行每列之间都有互相的约束关系。因此,我们可以用行和列作为另一个状态的部分。用一个新的方法表示行和列的状态:数字。考虑任何一个十进制数都可以转化成一个二进制数,而一行的状态就可以表示成这样——例如:1010(2)

个人的理解
原来是数组 : 第i行 用dp[ i ] = { 1 , 0 , 1 , 0 };
现在只需要一个数 10 就可以了,10的二进制为 1010;

【例题1】骑士(P1896 [SCOI2005]互不侵犯)

题目描述

在 n×n(1<=n<=10) 的棋盘上放 k(0<=k<n×n)个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。

输入格式

输入有多组方案,每组数据只有一行,包含两个整数 n 和 k。

输出格式

每组数据一行为方案总数,若不能够放置则输出 0。

输入样例

3 2

4 4

样例输出

16

79

原题解释:
我们的三个状态就有了:第几行(用i表示)、此行放什么状态(用j表示)、包括这一行已经使用了的国王数(用s表示)。

考虑状态转移方程。我们预先处理出每一个状态(s[x])其中包含二进制下1的个数,及此状态下这一行放的国王个数(num[x]),于是就有:

f[ i ][ j ][ s ] = sum ( f [ i−1 ][ k ][ s − num [ j ] ] ) , f[ i ][ j ][ s ]就表示在只考虑前i行时,在前i行(包括第i行)有且仅有s个国王,且第i行国王的情况是编号为j的状态时情况的总数。而k就代表第i-1行的国王情况的状态编号

个人的理解
首先,i是表示行数,j是表示编号记录用,s是表示运行状态,有多少个国王,进程如何用
他写的f[ i ][ j ][ s ] = sum ( f [ i−1 ][ k ][ s − num [ j ] ] )
看的太糊了
实际上就是这个:
f[ i ][ j ][ k ] += f [ i-1 ][ j’ ] [ k’ ]

状压DP 就是将一个状态 转化成 一个数,用一个数表示出来状态,然后再用位运算去进行对状态的处理

对于相邻边上下是否重合,作这样的位运算

1010 & 0010 >>> 0010 != 0 不符合要求
10001 & 00100 >>> 00000 == 0 符合要求

对左上,右下是否重合

10001000 >> 1 == 01000100
01000000 & 01000100 >>> 01000000 != 0 不符合要求
10010010 >> 1 == 01001001
00000001 & 01001001 >>> 00000001 != 0 不符合要求

100010 >> 1 == 010001
001000 & 010001 == 0 符合要求

对右上,左下是否重合

把 >> 换成 << 就ok

对自己一行是否符合条件,位运算如下

1010 >> 1 == 0101
1010 & 0101 == 0 符合要求
1100 >> 1 == 0110
1100 & 0110 == 0100 != 0 不符合要求

原题原博客的那个图的代码:

#include<bits/stdc++.h>

using namespace std;

long long f[11][155][155], ans; int num[155],s[155],N,K,s0;

void pre(){
    
    
	
	int i,j,k;
	s0 = 0;
	ans = 0;
	
	memset(f,0,sizeof f);
	
	for(i=0; i<(1<<N) ;i++){
    
    
		
		if(i& (i<<1))continue;
		
		k = 0;
		for(j=0; j<N ;j++) if(1& (1<<j)) k++;
		s[++ s0] = i;
		num [s0] = k;
	}
}

void DP(){
    
    
	
	int i,j,k,t;
	f[0][1][0] = 1;
	
	for(i=1 ; i<=N ;i++)
		for(j=1 ; j<=s0 ; j++)
		for(k=0 ; k<=K; k++)
			if(k > num[j])
				for(t=1 ; t<= s0 ;t++)
					if( !(s[t] & s[j] && !(s[t] & s[j]<<1)) &&!(s[t] & (s[j] >> 1)))
						f[i][j][k] += f[i-1][t][k-num[j]];
		
	for(i=1 ; i<=s0 ;i++) ans += f[N][i][K];
	cout << ans << endl;
}

int main(){
    
    
	while(cin >> N >> K)per(),DP();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_52559308/article/details/115918148
今日推荐