洛谷 P1896 [SCOI2005]互不侵犯

题目链接:洛谷 P1896 [SCOI2005]互不侵犯

算法标签: 动态规划(DP)状态压缩

题目

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

输入输出样例

输入 #1

3 2

输出 #1

16

题解:

状压DP

首先解释为什么是状压DP:

  • 根据题意我们能够得出是通过DP解决
  • 根据题中数据范围\(1 \le N \le 9\)

其次,我们要考虑DP的几个属性:

  • 状态:

    1. 我们定义\(f[i][j][s]\)为当前考虑前\(i\)行,第\(i\)行的国王的情况是编号为\(j\)的状态(压缩),在这\(i\)行共有\(s\)个国王的情况的总数。
    2. \(sit[]\)\(gs[]\)分别表示某一点的状态和此状态下这一行放的国王数量。这里需要有dfs预处理。
    void dfs(int sum, int tot, int now)
    {
      if (now >= n)
      {
          sit[ ++ cnt] = sum;
          gs[cnt] = tot;
          return ;
      }
      dfs(sum, tot, now + 1);
      dfs(sum + (1 << now), tot + 1, now + 2);
    }
  • 转移:

    1. 同一行国王的冲突:我们可以直接在预处理时候解决。
    2. 上下国王的冲突:if (sit[j] & sit[k]) continue ;​
    3. 左上方和右下方国王的冲突:if((sit[j] << 1) & sit[k]) continue ;
    4. 左下方和右上方国王的冲突:if (sit[j] & (sit[k] << 1)) continue ;
    5. 最终的转移过程按照直接处理每一行即可。
    for (int i = 2; i <= n; i ++ )
    {
      for (int j = 1; j <= cnt; j ++ )
      {
          for (int k = 1; k <= cnt; k ++ )
          {
              if (sit[j] & sit[k])
                  continue ;
              if ((sit[j] << 1) & sit[k])
                  continue ;
              if (sit[j] & (sit[k] << 1))
                  continue ;
              for (int l = gs[j]; l <= m; l ++ )
                  f[i][j][l] += f[i - 1][k][l - gs[j]];
          }
      }
    }

AC代码

#include <bits/stdc++.h>

using namespace std;

const int N = 10;

const int M = 110;

const int NM = 2020;

int n, m, sit[NM], gs[NM], cnt;
long long f[N][NM][M];
void dfs(int sum, int tot, int now)
{
    if (now >= n)
    {
        sit[ ++ cnt] = sum;
        gs[cnt] = tot;
        return ;
    }
    dfs(sum, tot, now + 1);
    dfs(sum + (1 << now), tot + 1, now + 2);
}
int main()
{
    scanf("%d%d", &n, &m);
    dfs(0, 0, 0);
    for (int i = 1; i <= cnt; i ++ )
    {
        f[1][i][gs[i]] = 1;
    }
    for (int i = 2; i <= n; i ++ )
    {
        for (int j = 1; j <= cnt; j ++ )
        {
            for (int k = 1; k <= cnt; k ++ )
            {
                if (sit[j] & sit[k])
                    continue ;
                if ((sit[j] << 1) & sit[k])
                    continue ;
                if (sit[j] & (sit[k] << 1))
                    continue ;
                for (int l = gs[j]; l <= m; l ++ )
                    f[i][j][l] += f[i - 1][k][l - gs[j]];
            }
        }
    }
    long long ans = 0;
    //printf("%d\n", cnt);
    for (int i = 1; i <= cnt; i ++ )
        ans += f[n][i][m];
    printf("%lld\n", ans);
    return 0;
}
01-02 00:34