luogu P1896 [SCOI2005] 互不侵犯 题解
思路
状态压缩 dp 。
状态压缩 dp
对于每一行,用一个 \(n\) 位二进制数表示每行的状态,则对于上下两行之间,设上行的数字为 \(a\) ,下行的数字为 \(b\) ,状态不合法有三种情况:
- \(a \operatorname{and} b \neq 0\) ,即存在上行与下行同一列都有国王。
- \((a << 1) \operatorname{and} b \neq 0\) ,即上行存在国王在下行国王右上方。
- \(a \operatorname{and} (b << 1) \neq 0\) ,即上行存在国王在下行国王左上方。
定义数组 \(f_{i,j,k}\) 表示第 \(i\) 行的状态为 \(j\) 时已经有 \(k\) 个国王时的方案数, 集合 \(S\) 表示对于下行上行合法的状态的集合, \(ones_i\) 表示 \(i\) 在二进制下有多少位 \(1\)。
易推出状态转移方程式 : \(f_{i, j, k} = \sum_l^{l \in S} f_{i - 1, l, k - ones_j}\) 。
对于每一行 \(i\) ,暴力枚举上下两行的所有状态即可。
细节
代码中的 \(j\) 并不是真正的状态,而是该状态对应的编号。
代码
时间复杂度 \(O(n\times2^{20})\)
#include <iostream>
#include <cstdio>
typedef long long LL;
using namespace std;
#define LF(i, __l, __r) for (int i = __l; i <= __r; i++)
#define RF(i, __r, __l) for (int i = __r; i >= __l; i--)
const int N = 600, M = 12, K = 105;
int n, k, cnt;
int st[N], ones[N];
LL f[M][N][K], ans;
void Init(int u, int sum, int sta) {
if (u >= n) {
st[++cnt] = sta;
ones[cnt] = sum;
return;
}
Init(u + 1, sum, sta);
Init(u + 2, sum + 1, sta | (1 << u));
}
int main() {
scanf("%d%d", &n, &k);
Init(0, 0, 0);
LF(i, 1, cnt) f[1][i][ones[i]] = 1;
LF(i, 2, n) LF(j, 1, cnt) LF(l, 1, cnt) {
if (st[j] & st[l]) continue;
if ((st[j] << 1) & st[l]) continue;
if (st[j] & (st[l] << 1)) continue;
RF(s, k, ones[j]) f[i][j][s] += f[i - 1][l][s - ones[j]];
}
LF(i, 1, cnt) ans += f[n][i][k];
printf("%lld", ans);
return 0;
}
子曰:“非其鬼而祭之,谄也。见义不为,无勇也。”