状态压缩 DP
状态压缩动态规划:
棋盘式 (基于连通性): 1064小国王 327玉米田 292炮兵阵地
集合: 略
1064 小国王
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数
重点
每一行的摆法,只受前一行的影响,前2及以上行的就没有影响了
动态规划
状态表示 f[i,j,k]
集合: 所有只摆在前 i 行,已经摆了 j 个国王,并且第i行摆放的状态是 s (二进制数
) 的选法
目标: count
状态计算
可转移的条件: a&b==0
AND (a|b)不能有两个相邻的1
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 #include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std ;
typedef long long LL ;
const int N = 12 , M = 1 << 10 , K = 110 ;
int n , m ;
vector < int > state ;
int cnt [ M ];
vector < int > head [ M ]; // 记录每行的“前驱”
LL f [ N ][ K ][ M ];
bool check ( int state ) // 判断state二进制表示里是否存在2个相邻的1
{
for ( int i = 0 ; i < n ; i ++ )
if (( state >> i & 1 ) && ( state >> i + 1 & 1 ))
return false ;
return true ;
}
int count ( int state ) // state的二进制表示里有多少个1
{
int res = 0 ;
for ( int i = 0 ; i < n ; i ++ ) res += state >> i & 1 ;
return res ;
}
/*
alias:
int count(int state) // state的二进制表示里有多少个1
{
int ans = 0;
for (int i=state; i; i -= (i&(-i)) ) ans++;
return ans;
}
*/
int main ()
{
cin >> n >> m ;
for ( int i = 0 ; i < 1 << n ; i ++ )
if ( check ( i )) // 合法才加入. "没有相邻的1"
{
state . push_back ( i );
cnt [ i ] = count ( i );
}
for ( int i = 0 ; i < state . size (); i ++ )
for ( int j = 0 ; j < state . size (); j ++ )
{
int a = state [ i ], b = state [ j ];
if (( a & b ) == 0 && check ( a | b )) // i 可以由 j 转移而来
head [ i ]. push_back ( 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 ])
{
// b -> a
int c = cnt [ state [ a ]];
if ( j >= c )
f [ i ][ j ][ a ] += f [ i - 1 ][ j - c ][ b ];
}
cout << f [ n + 1 ][ m ][ 0 ] << endl ;
return 0 ;
}
327 玉米田
农夫约翰的土地由 M×N 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。
动态规划
状态表示: f[i,s]
(摆了i行, 第i行状态是s)
集合: 所有已经摆完前i行, 且第i行的状态是s的所有摆放方案的集合
属性: count
状态计算:
依然是, 第i行只与第i-1行有关, 间隔行2及以上就没影响了
限制1: a, b 的二进制表示里不包含两个连续的1
限制2: (a & b) == 0
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 #include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std ;
const int N = 14 , M = 1 << 12 , mod = 1e8 ;
int n , m ;
int w [ N ];
vector < int > state ;
vector < int > head [ M ];
int f [ N ][ M ];
bool check ( int state )
{
for ( int i = 0 ; i + 1 < m ; i ++ )
if (( state >> i & 1 ) && ( state >> i + 1 & 1 ))
return false ;
return true ;
}
int main ()
{
cin >> n >> m ;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 0 ; j < m ; j ++ )
{
int t ;
cin >> t ;
w [ i ] += ! t * ( 1 << j ); // 统计哪些位是“不育地”, 这个写法太妙了
}
for ( int i = 0 ; i < 1 << m ; i ++ )
if ( check ( i ))
state . push_back ( i );
for ( int i = 0 ; i < state . size (); i ++ )
for ( int j = 0 ; j < state . size (); j ++ )
{
int a = state [ i ], b = state [ j ];
if ( ! ( a & b ))
head [ i ]. push_back ( j );
}
f [ 0 ][ 0 ] = 1 ;
for ( int i = 1 ; i <= n + 1 ; i ++ )
for ( int j = 0 ; j < state . size (); j ++ )
if ( ! ( state [ j ] & w [ i ]))
for ( int k : head [ j ])
f [ i ][ j ] = ( f [ i ][ j ] + f [ i - 1 ][ k ]) % mod ;
cout << f [ n + 1 ][ 0 ] << endl ;
return 0 ;
}
292 炮兵阵地
司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。
一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地(用 H
表示),也可能是平原(用 P
表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
动态规划
C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 #include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std ;
const int N = 10 , M = 1 << 10 ;
int n , m ;
int g [ 1010 ];
int f [ 2 ][ M ][ M ]; // 滚动数组非常简单, 先当啥也没有正常写, 随后再给这一维全&1
vector < int > state ;
int cnt [ M ];
bool check ( int state )
{
// 确保不存在两个“相差为2及以内”的“1”
for ( int i = 0 ; i < m ; i ++ )
if (( state >> i & 1 ) && (( state >> i + 1 & 1 ) || ( state >> i + 2 & 1 )))
return false ;
return true ;
}
int count ( int state )
{
int res = 0 ;
for ( int i = 0 ; i < m ; i ++ )
if ( state >> i & 1 )
res ++ ;
return res ;
}
int main ()
{
cin >> n >> m ;
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 0 ; j < m ; j ++ )
{
char c ;
cin >> c ;
g [ i ] += ( c == 'H' ) << j ; // 记录“不可育地”出现在这一行的哪一列
}
for ( int i = 0 ; i < 1 << m ; i ++ )
if ( check ( i ))
{
state . push_back ( 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 ++ )
{
// a: line i; b: line i-1; c: line i-2
int a = state [ j ], b = state [ k ], c = state [ u ];
if ( a & b | a & c | b & c ) continue ;
if ( g [ i ] & b | g [ i - 1 ] & a ) continue ;
f [ i & 1 ][ j ][ k ] = max ( f [ i & 1 ][ j ][ k ], f [ i - 1 & 1 ][ u ][ j ] + cnt [ b ]);
}
int res = 0 ;
for ( int i = 0 ; i < state . size (); i ++ )
for ( int j = 0 ; j < state . size (); j ++ )
res = max ( res , f [ n & 1 ][ i ][ j ]);
cout << res << endl ;
return 0 ;
}