[状压动规] [luoguP1896] [SCOI2005] 互不侵犯King
题目描述
在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。
输入输出格式
输入格式:
只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)
输出格式:
所得的方案数
输入输出样例
输入样例#1:
3 2
输出样例#1:
点击就送屠龙宝刀
16
俺的题解
也是一道状压的入门题目,大致结构与上道题一致。
最大的区别是多加了一维判断“已使用的国王数”的状态,不能超过K。
对于每一行的国王,考虑上一行与他不相邻的state,进行状态转移。
状态转移方程:
dp[当前行][枚举当前行的状态][当前行用的国王数+枚举的上一行用的国王数] += sigma(dp[上一行][枚举的上一行可行状态][枚举的上一行用的国王数])
俺的代码
1 /* 2 */ 3 #include<iostream> 4 #include<cstdio> 5 #include<cstring> 6 using namespace std; 7 int n, p, cnt = 0; 8 int state[1<<10], sum[150]; 9 long long dp[15][1<<10][100]; 10 11 12 int main() 13 { 14 memset(dp, 0, sizeof(dp)); 15 memset(sum, 0, sizeof(sum)); 16 scanf("%d%d", &n, &p); 17 for(int i = 0; i < (1 << n); i++) 18 { 19 if(! (i & i<<1)) 20 { 21 state[++cnt] = i; 22 int temp = i; 23 while(temp)//计算到达此状态用了的国王数 24 { 25 sum[cnt] += (temp % 2); 26 temp /= 2; 27 } 28 } 29 } 30 for(int i = 1; i <= cnt; i++) 31 { 32 if(sum[i] > p) 33 continue; 34 dp[1][state[i]][sum[i]]=1; 35 } 36 for(int i = 2; i <= n; i++) 37 { 38 for(int j = 1; j <= cnt; j++) 39 { 40 for(int k = 1; k <= cnt; k++) 41 { 42 if(state[j] & state[k]) 43 continue; 44 if(state[j] & (state[k] << 1)) 45 continue; 46 if((state[j] << 1) & state[k]) 47 continue; 48 for(int t = 1; t <= p; t++) 49 { 50 if(sum[j] + t > p) 51 continue; 52 dp[i][state[j]][sum[j] + t] += dp[i-1][state[k]][t]; 53 } 54 } 55 } 56 } 57 long long ans = 0; 58 for(int i = 1; i <= n; i++) 59 { 60 for(int j = 1; j <= cnt; j++) 61 { 62 ans += dp[i][state[j]][p]; 63 } 64 } 65 printf("%lld\n", ans); 66 return 0; 67 }
俺的反思
有一些细节上的问题耽误了很多时间,必须反思。
1、用乘法原理大概估计,会爆int。
2、爆完int后改long long,对应的输入输出类型也要更改。
3、因为对状态压缩没有熟练到一定程度,所以一个关键大括号写错了位置。需再熟练。
4、位运算的左移代表了2的i次方,没有预估好空间,定义时爆掉了。
在此记录,全局变量大约能开到4.9*108,当然这不代表比赛时可以开这么大。
5、状压的那一位,数据范围一般只能在15以内,如果远远高于15,数组直接爆了,别说别的操作了……
于是刚才有一道比较复杂的判重范围是500,激动地写了个状压的我真是不能再傻逼了。