更佳的阅读体验:2023 国庆集训 Day 4A 笔记,本文同步发表于 洛谷博客

以下内容为个人总结,如有错误或您有不同见解,欢迎指出。


状压 DP

洛谷 P4363 - 一双木棋

  • 只需要将当前局面下所放棋子的轮廓线压入状态即可,DP 初值为 $f[整个棋盘] = 0$。
  • 状压当前的轮廓线,依据这一步该谁下进行 min / max 搜索。

洛谷 P5369 - 最大前缀和

  • 统计每个子集作为最大前缀和的次数。
  • 如果某个前缀和是最大前缀和,那么这个前缀的所有后缀和都 $> 0$,后面的所有前缀和都 $\le 0$。
  • 设 $f[S]$ 为将 $S$ 排列成所有后缀 $> 0$ 的方案数,$g[S]$ 为将 $S$ 排列成所有前缀 $\le 0$ 的方案数。
  • 答案即为 $\sum sum[S]f[S]g[U-S]$,$U$ 为全集。

洛谷 P5492 - 随机算法

  • 求图上最大独立集。
  • 设 $f[S]$ 为 $S$ 内随机一个排列的正确方案数。
  • 枚举 $S$ 排列的第一个元素 $x$,加入 $x$ 会导致删除 $x$ 及与其相邻的点集 $c_x$。
  • 那么如果 $max⁡[S]=max⁡[S-c_x ]+1$,就令 $f[S]$ 加上 $f[S-c_x]$ 。
int ctz[1 << N];
long long f[1 << N], g[1 << N];
long long c[N];
const long long p = 998244353;

cin >> n >> m;

for (int i = 0; i < m; i++) {
    cin >> x >> y;
    c[x] |= 1 << y;
    c[y] |= 1 << x;
}

for (int i = 0; i < n; i++) {
    ctz[1 << i] = i;
}

for (int S = 1; S < 1 << n; S++) {
    int x = ctz[S & -S];
    g[S] = max(g[S - (1 << x)], g[S - (1 << x) - (c[x] & S)] + 1);
}

f[0] = 1;
for (int S = 1; S < 1 << n; S++) {
    for (int x = 0; x < n; x++) {
        if (~S >> x & 1) <=> if (!(S >> x & 1))
            continue;
        if (g[S - (1 << x) - (c[x] & S)] + 1 < g[S])
            continue;
        f[S] = (f[S] + f[S - (1 << x) - (c[x] & S)]) % p;
    }
}

cout << f[(1 << n) - 1] /n! << endl; 

图上拓扑序计数

给定一张 DAG,求其合法拓扑序个数。$n \le 20$,$m \le \frac{n(n - 1)}{2}$。

  • 设 $f(S)$ 表示 $S$ 诱导子图的拓扑序个数。
  • 转移时,枚举 $S$ 中拓扑序最靠前的节点 $i$:$f(S) = \sum_{i \in S, ind_i = 0}f(S-\{i\})$ 。其中 $ind_i=0$ 表示 $i$ 在 $S$ 中没有入度(而非整个图中)。
// topo count

long long f[1 << N];

bool valid(int x, int s) {
    // s 没有连向 x 的边
}

for (int s = 1; s < 1 << n; ++s) {
    for (int x = 0; x < n; ++x) {
        if (~s >> x & 1) continue;
        if (valid(x, s)) f[s] += f[s - (1 << x)];
    }
}

边子集拓扑序计数

给定一张有向图(不保证是 DAG)。设其边集为 $E$ 。对于 $T \subseteq E$ 定义 $f(T)$ 为保留 $T$ 时整张图的拓扑序数量,求 $∑_{T \subseteq E}f(T)$。$n \le 20$,$m \le n(n-1) $。

  • 设 $g(S)$ 表示 $S$ 诱导子图的答案,我们输出 $g(U)$。
  • 与其统计每个边集 $T$ 的拓扑序方案数,不如统计每种拓扑序能在多少种 $T$ 下合法。
  • 转移时仍然枚举 $S$ 中拓扑序最靠前的节点 $i$ 。
  • 要想让 $i$ 最靠前合法,就需要让 $i$ 在 $S$ 内的 $ind=0$。换句话说,不能保留从 $S-\{i\}$ 连向 $i$ 的边,而 $i$ 连向 $S-\{i\}$ 的边可以任意保留。
  • 所以 $g(S) = \sum_{i \in S} f(S-\{i\}) 2^{cnt_{i,S-\{i\}}}$,其中 $cnt_{i,S-{i}}$ 表示 $i$ 连向 $S-\{i\}$ 的边数。
// topo count with edge subset

long long count[N][1 << N];

cin >> n >> m;

for (int i = 0; i < m; ++i) {
    cin >> x >> y;
    for (int s = 1; s < 1 << n; ++s)
        if (s >> y & 1) ++count[x][s];
}

for (int s = 1; s < 1 << n; ++s)
    for (int x = 0; x < n; ++x) {
        if (~s >> x & 1) continue;
        f[s] += f[s - (1 << x)] << count[x][s - (1 << x)];
    }

洛谷 P2831 - 愤怒的小鸟

  • 每个优的抛物线至少经过两只猪。用 $\begin{cases} y_1 = ax_1^2 + bx_1 \\ y_2 = ax_2^2 + bx_2 \end{cases}$ 可解出一组 $(a, b)$。共有 $O(n^2)$ 组。预处理出每两只猪 $(i, j)$ 形成的抛物线能消灭哪些猪 $c[i][j]$ 。
  • 设 $f[S]$ 为消灭 $S$ 集合猪最少需要多少只小鸟。枚举第一条抛物线,则 $f[S] = \min⁡(f[S-c[i][j]]+1)$。
  • 固定 $i$ 为 $S$ 中的最小元,则只需枚举 $j$ 即可。
  • 复杂度为 $O(n2^n)$。

洛谷 P3959 - 宝藏

  • 对于 $v$ 相等的部分分,可以使用贪心求解。
  • 状态中需记录当前层数。设 $f[x][i][S]$ 为 $x$ 的子树编号集合为 $S$,$x$ 在第 $i$ 层的最小子树代价。
  • 得到转移方程 $f[x][i][S] = \min⁡(f[x][i][T]+f[y][i+1][S-T]+dis(x,y) \times i)$。
  • 取答案时枚举每个点作为起始点。
最后修改:2024 年 02 月 14 日
如果觉得我的文章对你有用,请随意赞赏