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;
}

子曰:“非其鬼而祭之,谄也。见义不为,无勇也。”

posted @ 2024-07-28 13:06  FRZ_29  阅读(7)  评论(0编辑  收藏  举报