状压DP Sgu223 骑士
本人水平有限,题解不到为处,请多多谅解
本蒟蒻谢谢大家观看
题目:
Problem K: Sgu223 骑士
Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 46 Solved: 25
[Submit][Status][Web Board]
Description
在一个N*N的棋盘上放K个KING,KING可攻击相邻的8个位置。
问有多少种方式让他们互不攻击。
N<=10,0<=k<=N*N
问有多少种方式让他们互不攻击。
N<=10,0<=k<=N*N
Input
第一行给出数字N,K
Output
如题
Sample Input
3 2
Sample Output
16
HINT
f[i][j][k]表示以第i行的第j个状态上放k个国王的每行的总方案数
f[11][145][101]是如何推出来的呢?
首先 第一维 是表示行数;由题意得:n<=10;所以行数只需到11;
其次 第二维 是表示每行的状态——例如:1 0 0 0 1 0 我们可以发现,任意一位是看
它前面那个数;
分情况:若前一位为0,则当前位可以任取,前两位可以任取;
若前一位为1,则当前位只能为0,前两位只能为0;
所以可得递推公式:f[i]=f[i-1]+f[i-2];(f[1]=2,f[2]=3)只有1位时,取何不取
两种情况;当有两位时,1 0 ,0 1,0 0;三种情况。
f[10]=144;我们第二维取145;
最后 第三维 是表示国王数;n<=10;n*n<=100;就算全部放上国王也就100;
所以,我们第三维取101;
code:
#include<bits/stdc++.h> #define ll long long using namespace std; ll f[11][145][101],ans; //f[i][j][k]表示以第i行的第j个状态上放k个国王的每行的总方案数 //1 0 0 0 1 →j //_ _ _ _ _ →i ll num[155], s[155], n, kk, k, sum; void find() { sum = 0, ans = 0; memset(f, 0, sizeof(f)); for (ll i = 0; i < (1 << n); i++) //此时i枚举状态,j枚举列数(把每行分成n块,则每块就是列数) { if (i & (i << 1)) //如果前后位置不能同时为1(同时放国王) //预处理:即先处理完行(前后) //那么进行DP转移时就不用再判断了 continue; kk = 0; for (ll j = 0; j < n; j++) { if (i & (1 << j)) kk++; //统计在状态为i的时候,对应位置有多少个1. 例如1001,有2个1 } s[++sum] = i;//相当与映射——把每一种状态转换为可以随时调用的数组 num[sum] = kk;//把每行所拥有的 1(国王数)记录下来 } } void dp() { f[0][1][0] = 1; //第0行可以放国王,但国王数为0,此时方案数为1 for (ll i = 1; i <= n; i++) //枚举行 { for (ll j = 1; j <= sum; j++) //枚举当前这一行的所有状态数() { for (ll t = 1; t <= sum; t++) //枚举上一行所有状态数 { for (ll kk = num[j]; kk <= k; kk++) //枚举前i行一共可以放多少个国王 //kk从num[j]开始,保证(kk-num[j])为正 { if (!(s[t] & s[j]) && !(s[t] & (s[j] << 1)) && !(s[t] & (s[j] >> 1))) //对于状态s[t]与s[j],上下不能同时为1(同时放国王) //并且对角不能同时为1(同时放国王) //每行我们不需要再进行判断,因为之前的预处理已经判断了 f[i][j][kk] += f[i - 1][t][kk - num[j]]; //上一行的上一个状态(当前所有的总国王数-当前这一行总国王数)累加 } } } } for (ll i = 1; i <= sum; i++) ans += f[n][i][k]; //把以1~n为行数的所有状态总国王数累加 printf("%lld", ans); } int main() { scanf("%lld%lld", &n,&k); find(); dp(); return 0; }