题307.状压dp-acwing-Q1064–小国王
一、题目
二、题解
用dp五步法分析该题:
1.确定dp数组,明确其含义。想着说用dp[i][j]表示放到了第i行,且已经使用了j个棋子时对应的方案数,但是其中的状态过于复杂,难以计算,于是将状态进一步分解,多开一个维度表示状态,则可用dp[i][j][a]表示放到第i行,共用了j个棋子,且最后一行对应的状态为下标为a的合法状态下对应的方案数。
2.确定递推公式。采用y式dp分析法如下:
则递推公式如下:dp[i][j][a]+=dp[i-1][j-count(a)][b];//a由b状态转移过来,b处于i-1行,且那个时候用了j-num个棋子
3.初始化dp数组。dp[0][0][0]=1;//显然放到第0行,放了0个棋子,状态为下标0对应的合法状态(其实就是0)方案数为1。其余为0
4.确定遍历顺序。i从前往后遍历行,j从前往后遍历棋子个数,a,b遍历状态
5.打印dp数组。
代码及详细注释如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;//防止数据溢出
const int maxn=12,maxm=1<<10,maxk=110;
//n最大其实是10,但是后期要用到11行,所以maxn就整到了12,m表示状态个数,显然由于1行是10列,所以状态个数最多有2^10个,maxk为最大棋数
int N,K;
vector<int> state;//存储所有合法状态,对应棋盘上的一行,相邻之间不能同时为1
vector<int> head[maxm];//head[i]存储i下标对应的合法状态的上一个合法状态对应的下标
int cnt[maxm];//cnt[i]表示表示下标为i的合法状态所含的1的个数,即棋子的个数
ll dp[maxn][maxk][maxm];//dp[i][j][a]表示放到第i行,共用了j个棋子,且最后一行对应的状态为下标为a的合法状态下对应的方案数
int check(int state)//检查状态state每相邻两个数是否同时为1
{
for(int i=0;i<N-1;i++)//最多移动N-1位(i+1=N-1)
{
//右移i位直接将state这个数表示的二进制状态中第i位上的数移动到了最右边,与上1后如果等于1则第i位上数为1否则为0
if((state>>i&1)&&(state>>i+1&1))//循环直接看第i位和第i+1位
{
return 0;
}
}
return 1;
}
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>>K;
//1.预处理
for(int i=0;i<1<<N;i++)//枚举到2^N
{
if(check(i))//判断状态是否合法
{
state.push_back(i);//存入合法状态
cnt[i]=count(i);//记录该合法状态1的个数
}
}
for(int i=0;i<state.size();i++)
{
for(int j=0;j<state.size();j++)//i,j遍历每个合法状态
{
int a=state[i],b=state[j];
if((a&b)==0&&check(a|b))//看b到a(上->下)是否可达,两种状态对应的二进制数每一位对应数不能同时为1,且相邻位不能同时为1
{
head[i].push_back(j);//j->i
}
}
}
//2.dp
dp[0][0][0]=1;//显然放到第0行,放了0个棋子,状态为下标0对应的合法状态(其实就是0)方案数为1
for(int i=1;i<=N+1;i++)//i遍历行数,有意遍历到N+1,使得答案能够被存入N+1行
{
for(int j=0;j<=K;j++)//j遍历棋子个数
{
for(int a=0;a<state.size();a++)//a遍历该行状态
{
for(int b:head[a])//b为该状态对应的上一层状态
{
int num=cnt[state[a]];//该行状态对应的1的个数
if(j>=num)//这1的个数当然不能超过棋子的个数
{
dp[i][j][a]+=dp[i-1][j-num][b];//a由b状态转移过来,b处于i-1行,且那个时候用了j-num个棋子
}
}
}
}
}
cout<<dp[N+1][K][0];//N+1行状态为什么棋子也不放,且那个时候共使用了K个棋子,对应的方案数就是答案
}