状态压缩是设计dp状态的一种方式。c++
当普通的dp状态维数不少(或者说维数与输入数据有关),但每一维总量不多时,能够将多维状态压缩为一维来记录。spa
这种题目最明显的特征就是:都存在某一给定信息的范围很是小(在20之内),而咱们在dp中所谓压缩的就是这一信息。设计
(或者是在作题过程当中分析出了某一信息种类数不多)3d
咱们来看个例子。code
经典题blog
给出一个n*m的棋盘,要放上一些棋子,要求不能有任意两个棋子相邻。求方案数。it
n<=100;class
m<=8。二进制
若是m固定的话能够设f[i][0/1][0/1]...[0/1]表示每一行每一列放不放im
若是不是固定的呢?
咱们发现后面的多个0/1能够当作一个二进制数
那咱们不就能够用数字代替后面的维数吗?
f[i][s]->f[i+1][s’](s&s’==0)
你会发现这样记录很暴力,状态数是与m相关的指数级的,但同时也就是由于m小咱们就确实能够这么作。
其实本质就是很暴力的记录状态,只不过利用了题目自己的特殊条件(这一维很小),使得咱们并不会所以复杂度太高。
同时也就是说,若是题目自己没有这样一个较小的信息,就不能应用状态压缩。
状态压缩dp确定是有一维是指数级的,这正是状态压缩的特色。
来看一道题:
P1896 [SCOI2005]互不侵犯
这个题能够状压DP的很明显的标志就是数据范围
咱们设f[i][j][k]表示当前在第i行,这一行及以前总共放了j个国王,当前的状态是k
那么咱们只要枚举行,而后再枚举状态转移就能够了
怎么判断互不侵犯?
用位运算就能够了
注意最后答案不能光统计最后一行,由于不必定在最后一行才用完全部的国王
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; int n,king; ll f[10][100][2000]; int s[2000],num[2000]; int cnt; ll ans; inline void pre() { int tot=(1<<n)-1; for(int i=0;i<=tot;i++) { if(!((i<<1)&i)) { s[++cnt]=i; int k=i; while(k) { num[cnt]+=k%2; k/=2; } } } } inline void dp() { for(int i=1;i<=cnt;i++) { if(num[i]<=king) f[1][num[i]][i]=1; } for(int i=2;i<=n;i++) { for(int j=1;j<=cnt;j++) { for(int k=1;k<=cnt;k++) { if((!(s[k]&s[j]))&&(!((s[k]<<1)&s[j]))&&(!(s[k]&(s[j]<<1)))) { for(int use=1;use<=king-num[j];use++) { f[i][num[j]+use][j]+=f[i-1][use][k]; } } } } } } int main() { scanf("%d%d",&n,&king); pre(); dp(); for(int i=1;i<=n;i++) { for(int j=1;j<=cnt;j++) { ans+=f[i][king][j]; } } cout<<ans; }