思想
主要是状态数量很多时,将状态转换成二进制数01010串,在将这些串在十进制中使用。
- 特点:一般数据范围不会超过20。
没啥好说的,来看看题吧
例题
P2704 [NOI2001] 炮兵阵地
思路
- 看到数据范围10*100不规则,还有个10,应该是状压(
- 我们将每行的状态设置为0~(1<<10),表示是否在该点上放置守卫,由于每个炮兵能够攻击到上下2格的位置,那么炮兵能影响的是三行的位置。我们需要设置dp[n][cur][pre],表示当前到了第n行,同时该行的状态时cur,上一行的状态时pre,之后进行转移即可
- 如何判断一行间两个炮兵距离大于2?可以判断(x & (x << 1)) || (x & (x << 2))即可
- 设置dp[105][1<<10][1<<10]会MLE,学到了个新方法,设置滚动数组可以直接将那一维mod滚动的数量,比如这题可以直接开dp[3][1<<10][1<<10],之后每次数值都是%3,就刚好能够自动滚。
- 详情看代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
int g[105][11];
int G[105];
int dp[5][1 << 10][1 << 10];
int Sum[1 << 11];
bool judge(int x)
{
if ((x & (x << 1)) == 0 && ((x & (x << 2)) == 0)){
return 1;
}
return 0;
}
void ini()
{
for (int i = 0; i < (1 << m); i ++){ //初始化一个状态有多少个1
int sum = 0;
int t = i;
while (t){
int tt = t % 2;
t /= 2;
if (tt) sum ++;
}
Sum[i] = sum;
}
for (int i = 0; i < n; i ++){ //初始化每一行的山地平地数据
int sum = 0;
for (int j = 1; j <= m; j ++){
sum *= 2;
sum += g[i][j];
}
G[i] = sum;
}
for (int i = 0; i < (1 << m); i ++){ //初始化第一行
if ((i & G[0]) || !judge(i)) continue;
dp[0][i][0] = Sum[i];
}
for (int i = 0; i < (1 << m); i ++){ //初始化第二行
if ((i & G[1]) || !judge(i)) continue;
for (int j = 0; j < (1 << m); j ++){
if ((j & G[0]) || !judge(j)) continue;
if (i & j) continue;
dp[1][i][j] = Sum[i] + Sum[j];
}
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++){
for (int j = 1; j <= m; j ++){
char c; cin >> c;
if (c == 'H') g[i][j] = 1;
}
}
ini();
for (int i = 2; i < n; i ++){
for (int j = 0; j < (1 << m); j ++){ //this
if ((j & G[i]) || !judge(j)) continue;
for (int k = 0; k < (1 << m); k ++){ //pre
if ((k & G[i - 1]) || !judge(k)) continue;
for (int p = 0; p < (1 << m); p ++){ //prepre
if ((p & G[i - 2]) || !judge(p)) continue;
if ((j & k) || (j & p) || (k & p)) continue;
dp[i % 3][j][k] = max(dp[i % 3][j][k], dp[(i + 2) % 3][k][p] + Sum[j]);
}
}
}
}
int maxx = 0;
for (int i = 0; i < (1 << m); i ++){
for (int j = 0; j < (1 << m); j ++){
maxx = max(maxx, dp[(n + 2) % 3][i][j]);
}
}
cout << maxx << endl;
return 0;
}