状压dp(模版及基础例题)(P1896 [SCOI2005]互不侵犯)
作者:互联网
状压dp:
简单来说,就是基于集合的dp。本题中状态转移取决于走过的点集和当前位置(最终答案由走完了所有点的状态再回到起点)。
状压就在于记录走过的点集的状态,相当于用二进制表示。
假设0表示该点没走过,1表示走过,对于一个四个点的情况,共有16种状态
简单地说:就是用01表示一个点(位置)的使用情况,并把整个情况压缩成一个二进制数,
通过处理这个二进制数,就能用更少的运算次数和空间复杂度完成状态的转移
(其实状压DP不过就是将一个状态转化成一个数,然后用位运算进行状态的处理)
示例:对于一个n^2的二维01矩阵
0 0 0 0
0 0 1 0
1 0 1 0
1 1 1 1
压缩之后,变为1111010101000000,存进数组就是把这个二进制数存成十进制数
注意:由于对于一个数,是从最小位开始,所以把状态写成二进制数也是从最小位开始
例题:洛谷
P1896 [SCOI2005]互不侵犯
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
注:数据有加强(2018/4/25)
输入格式
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式
所得的方案数
输入输出样例
输入 #13 2输出 #1
16
#include<cstdio> using namespace std; const int N = 12; long long dp[N][1<<N][N*N];//前i行,状态为j,放了k个国王 int n, k, cnt; long long ans; int s[1<<N],num[1<<N];//s统计编号为j的状态压缩所产生的二进制数 int main()//num统计编号为j的状态,所放置的国王数 { scanf("%d%d", &n, &k); for(int i = 0;i<(1<<n);i++)//i是当前态压缩所产生的二进制数 { if(i&(i<<1)) continue;//如果正上方已经放了一个国王,这种情况就显然不可取 int sum = 0; for(int end = 0;end < n;end++) { if(i&(1<<end)) sum++;//在二进制数上依次向左找,统计共有几个国王 } s[++cnt] = i; num[cnt] = sum; }//预处理 (这一次预处理只要保证i的位置正上方没有国王就可以了) dp[0][1][0] = 1; for(int i = 1;i <= n;i++) { for(int j = 1;j <= cnt;j++)//枚举状态(状态的编号) { for(int p = 0;p <= k;p++) { if(p>=num[j])//num没有超过k(超过了就显然出错了啊) { for(int end=1;end<=cnt;end++)//枚举第i-1行可行的状态编号 { if(!(s[end] & s[j]) && !(s[end] & (s[j] << 1)) && !(s[end] & (s[j] >> 1)))//从第i-1行向第i行转移 { dp[i][j][p] += dp[i-1][end][p-num[j]];//加和 } } } } } } for(int i = 1;i <= cnt;i++) { ans += dp[n][i][k];//把所有的状态数和起来 } printf("%lld", ans); return 0; }
标签:状态,P1896,二进制,状压,int,num,例题,dp 来源: https://www.cnblogs.com/mint-hexagram/p/14766142.html