一般处理方式
所谓状态压缩dp,就是用二进制表示状态,再转换成十进制,方便存储
例如有一排灯,开着的可以用 1 表示,关着的可以用 0 表示,
1011表示第1,3,4灯亮着,2 关着,所以这种状态可以用一个十进制11表示这种状态,进而遇到具体情况进行其他操作。
一般进行操作的模板:
- 暴力搜索所有的行的状态,将合法的行筛进state(vector)中
- 根据题意,将相邻行之间的所有合法配对方式存到二维数组中
- 根据题意进行dp
例题一:小国王
在 n×n 的棋盘上放 k 个国王,国王可攻击相邻的 8 个格子,求使它们无法互相攻击的方案总数。
输入格式
共一行,包含两个整数 n 和 k。
输出格式
共一行,表示方案总数,若不能够放置则输出0。
数据范围
1≤n≤10,
0≤k≤n2
输入样例:
3 2
输出样例:
16
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=21;
const int M=1<<10,K=110;
LL f[N][K][M];
int cnt[M];
vector<int>state;
vector<int>head[M];
int n,m;
bool check(int state)//判断每一行的状态是否合法
{
for(int i=0;i<n;i++)
{
if((state>>i&1)&&(state>>i+1&1))//每一行左右相邻的格子不能有国王
return false;
}
return true;
}
int count(int state)//二进制中1的个数,即每一行国王的数量
{
int res=0;
for(int i=0;i<n;i++)
{
res+=state>>i&1;
}
return res;
}
int main()
{
cin>>n>>m;
for(int i=0;i<1<<n;i++)
{
if(check(i))
{
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)&&check(a|b))
{
head[a].push_back(b);//筛选上下相邻合格的两行
}
}
}
f[0][0][0]=1;
for(int i=1;i<=n+1;i++)
{
for(int j=0;j<=m;j++)
{
for(auto x:state)
{
for(auto y:head[x])
{
if(j>=cnt[x])
{
f[i][j][x]+=f[i-1][j-cnt[x]][y];
}
}
}
}
}
cout<<f[n+1][m][0]<<endl;
return 0;
}
例题二:玉米田
农夫约翰的土地由 M×N 个小方格组成,现在他要在土地里种植玉米。
非常遗憾,部分土地是不育的,无法种植。
而且,相邻的土地不能同时种植玉米,也就是说种植玉米的所有方格之间都不会有公共边缘。
现在给定土地的大小,请你求出共有多少种种植方法。
土地上什么都不种也算一种方法。
输入格式
第 1 行包含两个整数 M 和 N。
第 2…M+1 行:每行包含 N 个整数 0 或 1,用来描述整个土地的状况,1 表示该块土地肥沃,0 表示该块土地不育。
输出格式
输出总种植方法对 108 取模后的值。
数据范围
1≤M,N≤12
输入样例:
2 3
1 1 1
0 1 0
输出样例:
9
#include <bits/stdc++.h>
using namespace std;
const int N=15;
const int M=1<<12;
const int mod=1e8;
int w[N];
vector<int>state;
vector<int>head[M];
int f[N][M];//第i行状态为j
int n,m;
bool check(int state)
{
for(int i=0;i<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[a].push_back(b);
}
}
f[0][0]=1;
for(int i=1;i<=n+1;i++)
{
for(auto x:state)
{
if(!(x&w[i]))//筛选合法的状态,当前层能种植0,不能种植1,枚举状态的1是可以种植,0不能种植,&运算为0为合法方案
{
for(auto y:head[x])
{
f[i][x]=(f[i][x]+f[i-1][y])%mod;
}
}
}
}
cout<<f[n+1][0]<<endl;
return 0;
}
例题三:炮兵阵地
司令部的将军们打算在 N×M 的网格地图上部署他们的炮兵部队。
一个 N×M 的地图由 N 行 M 列组成,地图的每一格可能是山地(用 H 表示),也可能是平原(用 P 表示),如下图。
在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。
图上其它白色网格均攻击不到。
从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
输入格式
第一行包含两个由空格分割开的正整数,分别表示 N 和 M;
接下来的 N 行,每一行含有连续的 M 个字符(P 或者 H),中间没有空格。按顺序表示地图中每一行的数据。
输出格式
仅一行,包含一个整数 K,表示最多能摆放的炮兵部队的数量。
数据范围
N≤100,M≤10
输入样例:
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
输出样例:
6
#include <bits/stdc++.h>
using namespace std;
const int N=10,M=1<<10;
int n,m;
int g[1010];
int f[2][M][M];
vector<int>state;
vector<int>head[M];
int cnt[N];
bool check(int state)
{
return !(state&state>>1||state&state>>2);
}
int count(int state)
{
int res=0;
for(int i=0;i<m;i++)
{
res+=state>>i&1;
}
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 (auto x : state)
for (auto y : state)
if (!(x & y))
head[x].push_back(y);
for (int i = 1; i <= n + 2; i++)
for (auto x : state)
if (!(x & g[i]))
for (auto y : head[x])
for (auto z : head[y])
if (!(x & z))
f[i&1][x][y] = max(f[i&1][x][y], f[i - 1&1][y][z] + cnt[x]);
cout<<f[n+2&1][0][0]<<endl;
return 0;
}