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;
}
子曰:“非其鬼而祭之,諂也。見義不為,無勇也。”