luogu P1896 [SCOI2005] 互不侵犯 題解

FRZ_29發表於2024-07-28

luogu P1896 [SCOI2005] 互不侵犯 題解

題目傳送門

思路

狀態壓縮 dp 。

狀態壓縮 dp

對於每一行,用一個 \(n\) 位二進位制數表示每行的狀態,則對於上下兩行之間,設上行的數字為 \(a\) ,下行的數字為 \(b\) ,狀態不合法有三種情況:

  1. \(a \operatorname{and} b \neq 0\) ,即存在上行與下行同一列都有國王。
  2. \((a << 1) \operatorname{and} b \neq 0\) ,即上行存在國王在下行國王右上方。
  3. \(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;
}

子曰:“非其鬼而祭之,諂也。見義不為,無勇也。”

相關文章