简而言之,状态压缩就是用进制数(可以是二进制,三进制等等)表示状态,然后进行dp。
题目描述:https://www.luogu.org/problemnew/show/P1896
题意大致就是,在一个棋盘里放一定数量的国王,一个国王的周围没有国王,询问方案数。
这是个简单的状压dp(真简单呐~)
注:&运算法则,上下如果都是1,则为1.
我们可以用1表示在棋盘的格点上表示有国王,0表示在棋盘的格点上没有国王。因为每个格点只有两种状态,所以我们可以用二进制数来表示每一行的状态。例如:
假设这是第i行国王的摆放情况,我们可以用10(1*2^1+1*2^4)来表示这一行的状态. 不难知道,一共有2^n种状态,也就是(0~2^n-1)。
我们可以先判断一行中的状态合不合法,若a&(a>>1)或者a&(a<<1)不为零,则此状态不合法。为什么呢??
见图
不难看出如果一个国王左右相邻有国王,则a&(a>>1)或者a&(a<<1)不为零。
这样我们就可以预处理行内的所有合法状态,然后直接用合法状态进行转移(优化时间复杂度):
for(int i=0;i<=(1<
横行判断完之后,判断纵行合不合法。不妨先判断上下两行合不合法(令上一行为a,本行为b)
如果(a&b)不为零,则不合法。
上下紧挨的有国王不合法。
如果 ((a>>1)&b)不为零,则不合法。
本行的左上有国王。
同样,如果右上有国王,也不合法。
条件判断完之后考虑状态转移:
dp[i][j][k] i表示安放的第几行,j表示第i行的状态,k表示已经安放的国王数。
for(ll i=0;i<=n-1;i++)//枚举行数
for(ll j=1;j<=cnt;j++)//预处理完了的每行的合法状态,枚举本行的合法状态
for(ll k=0;k<=m;k++)//枚举已经安放的国王数
{
for(ll l=1;l<=cnt;l++){ //枚举下一行
if(合法){
dp[i+1][l][k+(l行的国王数)]+=dp[i][j][k];//下一行以l为状态,k+(l行的国王数)的已经安放的国王的方案可以被本行以j为状态,k个国王所转移
}
}
}
最后输出dp[n][(可以以任意合法状态结尾)][m]就行了
for(ll i=1;i<=cnt;i++)
ans+=dp[n][i][m];
printf("%lld",ans);
如需参考,详见全部代码:
#include
#include
#include
#define ll long long
using namespace std;
long long n,k,dp[10][1000][100],m,num[1000],cnt,zt[1000];
long long work(long long x){
long long ans=0;
while(x!=0){
if(x%2) ans++;
x=x/2;
}
return ans;
}
long long judge(long long a){
if(a&(a<<1)||a&(a>>1)) return 1;
return 0;
}
long long work2(long long a,long long b){
if(!(a&(b<<1))&&!(a&(b>>1))&&!(a&b)) return 0;
return 1;
}
int main(){
long long ans=0;
scanf("%lld%lld",&n,&m);
for(long long i=1;i<=1000;i++)
num[i]=work(i);
dp[0][1][0]=1;
cnt=0;
for(ll i=0;i<=(1<
本题算是一个比较普通的状压吧,条件比较好想。
以上算是个引子:(前方高能!)
状压的动态规划: 一般的动态规划,也就是从整体中提取两三个关键的信息,依此划分阶段使问题不具备后效性及最优子结构的性质。但有时,保存的信息不足以进行决策的时候,许多元素都会直接影响到决策,都需要被考虑到。为每一个元素的状态都开一维数组来存储肯定是行不通的,于是就需要有一种便于操作的几下各个元素的状态,即进行状态压缩存储,也就有了状态压缩的动态规划(需细细品味!!!!!!!)。
也有比较复杂的条件判断:例如洛谷P5005 中国象棋—摆上马(也不算是很复杂)
此题还需要考虑(俗话说的)别马脚(由于好写但是很难说,所以直接撂下代码)
#include
#include
using namespace std;
const int p=1e9+7;
int dp[105][70][70],n;
int work(int x,int y){
int w=1;
int a=(1<<(n-1));
while(w<=n){
if(((y>>(w-1))&1)&&(!((y>>w)&1))&&(((x>>(w+1))&1))) return 1;
if(((x>>(w-1))&1)&&(!((x>>w)&1))&&(((y>>(w+1))&1))) return 1;
if(((y<<(w-1))&a)&&(!((y<>(w-1))&1)&&(!((y>>(w-1))&1))&&(((x>>(w))&1))) return 1;
if(((x>>(w-1))&1)&&(!((y>>(w-1))&1))&&(((z>>(w))&1))) return 1;
if(((z<<(w-1))&a)&&(!((y<<(w-1))&a))&&(((x<<(w))&a))) return 1;
if(((x<<(w-1))&a)&&(!((y<<(w-1))&a))&&(((z<<(w))&a))) return 1;
w++;
}
return 0;
}
int main(){
int x,y;
scanf("%d%d",&x,&y);
n=y;
for(int i=0;i<=(1<
当然这不是满分程序,由于题开的内存太小,所以要开滚动数组,但这么写代码好理解。
类似的题还有很多,就不一 一列举了