【学习笔记】- 动态规划基础:状态压缩
喵喵放了状压的视频,跟着写了个学习笔记,不过话说还在更新中诶,先发出来真的好吗
一般用于解决棋盘式问题,是基于连通性的dp
用一个集合的格式来把 dp
问题分解掉
这里用例题来讲解状压
-
骑士/互不侵犯
-
状态表示
\(f_{i,j,s}\)表示前\(i\)行已经放完,已经放了\(j\)个棋子,同时最后一行的状态是\(s\)
-
集合
所有只摆在前\(i\)行,已经摆了\(j\)个国王,同时第 \(i\) 行摆放的状态是 \(s\) 的所有方案的集合
此处的\(s\)是一个二进制数,其中\(0\)表示没有国王,\(1\)表示有国王
-
属性
-
-
状态计算
对应的是一个集合划分的过程
转移到\(f_{i,j,s}\)的条件是
-
第\(i-1\)行内部不能有两个\(1\)相邻
-
第\(i-1\)行和第\(i\)行之间也不能相互攻击到
用\(a\)表示上一行的状态,\(b\)表示这一行的状态
唔,由此来看满足的条件也就是
(a&b)==0
且(a|b)不能有两个相邻的1
-
转移方法
已经摆完了前排且第\(i\)排的状态是\(a\),第\(i-1\)排的状态是\(b\),已经摆完了\(j\)的国王的所有方案 可以转化为 已经摆完了前\(i-1\)排,并且第\(i-1\)排的状态是\(b\),已经摆了
j-count(a)
个国王(这里的count
是二进制中\(i\)的个数)的所有方案也就是说
f[i,j,b]
可以从f[i-1,j-count(a),b]
转移来复杂度虽然是指数级的,但是我们发现\(n<9\)所以可以通过此题
-
-
代码
#include<bits/stdc++.h> #define int long long using namespace std; const int N=12,M=1<<10,K=110; int n,m; vector<int> state,head[M]; int id[M],cnt[M],f[N][K][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){ int res=0; for(int i=0;i<n;i++) res+=state>>i&1; return res; } signed 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)==0)&&check(a|b)) head[i].push_back(j); } } f[0][0][0]=1; for(int i=1;i<=n+1;i++){ for(int j=0;j<=m;j++){ for(int a=0;a<state.size();a++){ for(int b:head[a]){ int c=cnt[state[a]]; if(j>=c){ f[i][j][a]+=f[i-1][j-c][b]; } } } } } cout<<f[n+1][m][0]<<"\n"; }
-