[SCOI2005]互不侵犯
简化题意
让你在一个棋盘上放置 k 个棋子,每个棋子的八个方位不能有其他的棋子。
思路
用 \(f_{i, j, s}\) 表示,第 i 行放置的方式为 j,放置了多少个棋子。
用 \(sit_x\) 表示 x 中 1 的个数,可以预处理,以及当前状态下放置的国王的个数 gs 。
那么 \(f_{i,j,s} = \sum f_{i-1, k, s - gs_j}\)
转移的条件就是:
- \(sit_j \ \& \ sit_k\) 及上下有重复的棋子。
- \((sit_j << 1) \& sit_k\) 及左上右下有重复的棋子。
- \(sit_j \& (sit_k << 1)\) 及左下右上有重复的棋子。
以上条件如果都不满足,那么可以转移。
code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define N 110
#define M 20
using namespace std;
const int mod = 1e9+7;
const int inf = 0x3f3f3f3f;
int n, k, s0;
ll f[20][N][110], num[N], s[N];
int read() {
int s = 0, f = 0; char ch = getchar();
while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
return f ? -s : s;
}
int main() {
n = read(), k = read();
for (int i = 0; i < (1 << n); i++) {
if (i & (i << 1)) continue;
int kk = 0;
for (int j = 0; j < n; j++)
if (i & (1 << j)) kk++;
s[++s0] = i, num[s0] = kk;
}
f[0][1][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= s0; j++)
for (int kk = num[j]; kk <= k; kk++)
for (int t = 1; t <= s0; t++)
if (!(s[t] & s[j]) && !(s[t] & (s[j] >> 1)) && !(s[t] & (s[j] << 1)))
f[i][j][kk] += f[i - 1][t][kk - num[j]];
ll ans = 0;
for (int i = 1; i <= s0; i++) ans += f[n][i][k];
cout << ans;
}