个人总结!!(棋盘式状态压缩DP)
0.状态压缩dp的缺点&标志
毫无疑问,指数级时间复杂度——导致棋盘的大小一般为二位数,才能用状态压缩dp求解。
1.求解此类问题
棋盘式状态压缩DP本质上是在一个棋盘内填奇奇怪怪的不同形状的块,(然后求方案数?)
2.思维难度
要能看出这是动态规划的题目,并建立状态表示,思考出状态转移。
一般来说求解该问题可以用以下几点
0.一列一列转移,
1.遍历一遍所有状态,找出所有合法状态(具体题目具体分析)。
2.二维遍历所有状态,建立状态与状态之间的转移关系(具体题目具体分析)。(这种处理方式可以大大降低运行时间!)
3.根据状态表示的实际意义,多处理一列可以直接得到答案。
4.根据状态的实际意义初始化。
3.Ac代码
(写给自己看的注释 )
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 12,M = 1<<N;
int n,m;
ll f[N][M];
bool st[M];//是否合法
vector<int> state[M];//用于存放转移关系
void init()
{
memset(f,0,sizeof f);
memset(st,true,sizeof st);
}
int main()
{
while(cin>>n>>m ,n||m)
{
init();//初始化
//遍历求出合法状态
for(int i=0;i< 1<<n;i++)
{
int cnt=0;//记录该段0的个数
for(int j=0;j<n;j++)
{
if(i>>j & 1)//该段落结束——eg.010001
{
if(cnt & 1) {st[i]=false;break;}//如果这一段0的个数是奇数,则不合法
cnt=0;//归零
}
else
cnt++;}
if(cnt&1) st[i]=false;//111000——后缀全是0这种情况
}
//预处理出转移关系
for(int i=0;i< 1<<n;i++)
{state[i].clear();
for(int j=0;j< 1<<n;j++)
{
if((i & j) == 0 && st[i|j])//说明i可以转移至j。i,j可以相互转移
state[i].push_back(j);
}
}
f[0][0]=1;//边界情况,符合状态表示的实际含义
//开始dp
for(int i=1;i<=m;i++)//一列一列处理,而且要多处理一列
{
for(int j=0;j < 1<<n;j++)//遍历所有二进制状态
{
for(auto k:state[j])//遍历所有能合法转移至当前j状态的状态
{
f[i][j]+=f[i-1][k];//状态转移方程
}
}
}
cout<<f[m][0]<<endl;//多处理一列的意义,可以直接输出答案
}
return 0;
}
4.总结
之前学过的东西没有系统的整理、思考、刷例题、按知识点刷题单。