状压DP Sgu223 骑士

本人水平有限,题解不到为处,请多多谅解

本蒟蒻谢谢大家观看

题目:

Problem K: Sgu223 骑士

Time Limit: 1 Sec  Memory Limit: 128 MB
Submit: 46  Solved: 25
[Submit][Status][Web Board]

Description

在一个N*N的棋盘上放K个KING,KING可攻击相邻的8个位置。
问有多少种方式让他们互不攻击。
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;
}
原文地址:https://www.cnblogs.com/nlyzl/p/11309637.html