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

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


单调队列优化 DP

洛谷 P1886 - 单调队列

int a[N];
deque<int> q;  // 存每个数的下标

for (int i = 1; i <= n; ++i) {
    while (!q.empty() && q.front() <= i - k) q.pop_front();
    while (!q.empty() && a[q.back()] <= a[i])       q.pop_back();
    q.push_back(i);
    cout << a[q.front()] << '\n';
}

洛谷 P2216 - 理想的正方形

洛谷 P2219 - 修筑绿化带

  • 求出每一块花园中,花坛应该放在哪里。如果花园的右下角为 $(x, y)$,那么花坛的右下角就应位于 $(x - a + c + 1, y - b + d + 1) \sim (x - 1, y - 1)$ 这个矩形内。
  • 求出这个矩形内最小的 $sum$。

求最大全 0 正方形

给定一个 0 / 1 矩阵,求出最大的全为 0 的子正方形,求不带 $\log$ 的做法。

带 $\log$ 的做法:枚举每个点作为左上角,二分正方形边长。

  • 令 $f[i][j]$ 表示矩形 $(i, j)$ 位置往下延伸 $0$ 的个数,得到转移方程 $f[i][j] = \begin{cases} 0, a_{i, j} = 1 \\ f[i + 1][j] + 1, a_{i, j} = 0 \end{cases}$。
  • 枚举正方形的上边界 $u$,依次从 $1 \sim n$ 枚举正方形的右边界。
  • 维护一个左边界的指针,并用单调队列维护左右边界之中的 $f$ 值,需要时刻保证 $r - l + 1 \le \min ⁡f$ 。
  • 复杂度为 $O(n^2 )$。

带修版本:洛谷 P4259 - 寻找车位

洛谷 P2254 - 瑰丽华尔兹

  • 设 $f[t][i][j]$ 为 $t$ 时刻到达 $(i, j)$ 的最长路程。
  • 假如 $t$ 时刻钢琴是往上方滑动,那么 $f[t][i][j] = \max (f[t - 1][i + 1][j] + 1, f[t - 1][i][j])$。往其他方向同理。
  • 复杂度为 $O(nmT)$。
  • 由于时间可以被分为 $K$ 段,每段滑动方向相同,则可以修改状态,令 $f[k][i][j]$ 为第 $k$ 个时间段结束后到达 $(i, j)$ 的最长路程。
  • $f[k][i][j] = \max {f[k - 1][i + s][j] + s), 0 \le s \le t_k}$。使用单调队列优化。
  • 复杂度 $O(nmK)$。
// 不考虑障碍 且只考虑向上的转移

for (int k = 1; k <= K; ++k) {
    int t_k;
    for (int j = 1; j <= m; ++j) {
        deque<int> q;
        int g[];
        for (int i = 1; i <= n; ++i) g[i] = f[k - 1][i][j] + i;
        for (int i = n; i; --i) {
            while (!q.empty() && q.front() > i + t_k) q.pop_front();
            while (!q.empty() && g[q.back()] <= g[i]) q.pop_back()
            q.push_back(i);
            f[k][i][j] = g[q.front()] - i;
        }
    }
}

洛谷 P4381 - Island

  • 求若干个基环树的直径之和。
  • 对于一个基环树,找到它的环,求出环上每个节点 $i$ 环外延伸的最长距离 $d_i$。
  • 将长为 $m$ 的环破为长为 $2m$ 的链。如果从节点 $i$ 外面走到节点 $j$ 外面,则距离为 $j - i + d_i + d_j$,同时要求 $i < j < i + n$。使用单调队列优化求解。
  • 复杂度为 $O(n)$。

单调队列优化多重背包

一共有 $c_i$ 个 $i$ 号物品,有 $w_i$ 的价值和 $v_i$ 的体积。

洛谷 P5665 - 划分

  • 贪心地使最后一段的和尽可能小。
  • 不严谨证明:如果最后一段 $[l, r]$ 的和没到达下界,那么可以不断地把 $a_l$ 分给前一段。由于 $s_i < s_{i+1}$,分给前一段的贡献是 $2a_l s_i$,分给后一段的贡献是 $2a_l s_{i+1}$,则分给前一段更优,同时分给前一段也有利于后面的分段。
  • 所以设 $f[r]$ 为 $\max⁡ {l-1: 最后一段分成 [l, r] 可行}$,应有 $sum_r - sum_{l-1} \ge sum_{l-1}-sum_{f[l-1]}$,即 $sum_r \ge 2sum_{l-1} - sum_{f[l-1]}$,双指针即可。
  • 复杂度为 $O(n)$。

计数 DP

洛谷 P5824 - 十二重计数法

洛谷 P3702 - 序列计数

  • 用总方案数去掉不含质数的方案数。
  • 分别用 DP 求解,设 $f[i][j]$ 为 $i$ 个数字,和 $\mod p = j$ 的方案数,矩阵快速幂加速。
  • 复杂度 $O(p^3 \log⁡ n )$。

洛谷 P3773 - 吉夫特

  • 由 Lucas 定理可知 $C_n^m$ 是奇数 $↔(n\&m)=m$。
  • 设 $f[T]$ 表示结尾值为 $S$ 的序列个数,转移时枚举子集 $T$,如果 $pos[S] < pos[T]$ 则令 $f[T]$ 加上 $f[S]$。
  • 复杂度为 $O(3^w)$。
int a[N], pos[N];

cin >> n;
for (int i = 1; i <= n; ++i) {
    cin >> a[i];
    pos[a[i]] = i;
}

for (int s = VALUE_MAX; s; --s) {
    ++f[s];
    for (int t = (s - 1) & s; t; --t)
        if (pos[s] < pos[t]) f[t] += f[s];
       ans += f[s] - 1;
}

洛谷 P5664 - Emiya 家今天的饭

  • 对「每种食材最多在一半菜中」进行容斥。用总的方案数减去某种食材过多的方案数,不会有两种食材同时过多。
  • 总方案数易求。
  • 枚举过多的食材种类 $t$,设 $f[i][j][k]$ 表示前 $i$ 种烹饪方法,一共做了 $j$ 个菜,其中 $k$ 个使用 $t$ 的方案数。转移时枚举第 $i + 1$ 种烹饪方法是否选用,选用时是否使用 $t$,最后要求 $k > \frac{j}{2}$ 。
  • 只需记录 $2k - j$ 的值即可,最后要求此值 $> 0$。
  • 复杂度为 $O(n^2 m)$。
最后修改:2024 年 02 月 14 日
如果觉得我的文章对你有用,请随意赞赏