1 条题解
-
0
D2.逆序图染色(困难版本) 题解
一、与简单版本的区别
- 困难版本:,。
- 简单版本的 DP 不再可行,需要优化至 或 。
二、核心观察
回顾 D1 的 DP 状态: 表示当前子序列中最大值为 、被更大元素领先过的最大值为 的方案数。
转移方程:
- 不选当前元素: 自动继承。
- 选当前元素 :
- 若 :(对所有 )
- 若 :(对所有 )
注意到每次迭代只有 个位置被更新(第 行和第 列),而非整个矩阵。
三、优化思路
3.1 消除时间维度
由于"不选"操作自动继承上一层的值,我们可以直接在原地更新矩阵,无需存储 维度。
设当前矩阵为 ,对每个新元素 :
- 更新第 行:$ways[x][mp] \mathrel{+}= \sum_{m'=0}^{x-1} ways[m'][mp]$( 成为新的最大值)
- 更新第 列:$ways[m][x] \mathrel{+}= \sum_{mp'=0}^{x-1} ways[m][mp']$( 成为新的被领先最大值,仅对 )
3.2 数据结构加速
上述转移需要对每一行和每一列求前缀和。可以使用**树状数组(Fenwick Tree)**维护:
- 对每一行 维护一个树状数组,存储该行所有 的方案数。
- 对每一列 维护一个树状数组,存储该列所有 的方案数。
更新:
- 增加 → 在行 的树状数组位置 加 ,同时在列 的树状数组位置 加 。
查询:
- → 查询列 的树状数组前缀和到 。
- → 查询行 的树状数组前缀和到 。
四、算法流程
- 离散化:将原序列 压缩为排列 (保留偏序关系)。
- 初始化:,在行 和列 的树状数组中各加 。
- 对于每个 到 :
- 令 。
- 对于所有 ( 作为 ):
- ()
- 若 ,记录更新 增加 。
- 对于所有 ( 作为 ):
- ()
- 若 ,记录更新 增加 。
- 批量执行所有记录的更新(更新行和列树状数组)。
- 答案:$\sum_{m=0}^n \sum_{mp=0}^n ways[m][mp] = \sum_{m=0}^n \text{query\_行}_m(n)$。
五、代码实现(C++)
#include <bits/stdc++.h> using namespace std; const int MOD = 1e9 + 7; const int NMAX = 2005; int n; int a[NMAX], b[NMAX]; int bit_lin[NMAX][NMAX], bit_col[NMAX][NMAX]; void addSelf(int &x, int y) { x += y; if (x >= MOD) x -= MOD; } void update_lin(int lin, int pos, int val) { pos++; for (int i = pos; i <= n + 1; i += (i & -i)) addSelf(bit_lin[lin][i], val); } void update_col(int col, int pos, int val) { pos++; for (int i = pos; i <= n + 1; i += (i & -i)) addSelf(bit_col[col][i], val); } int query_lin(int lin, int pos) { pos++; int res = 0; for (int i = pos; i > 0; i -= (i & -i)) addSelf(res, bit_lin[lin][i]); return res; } int query_col(int col, int pos) { pos++; int res = 0; for (int i = pos; i > 0; i -= (i & -i)) addSelf(res, bit_col[col][i]); return res; } void solve() { cin >> n; for (int i = 1; i <= n; i++) cin >> b[i]; // 离散化 for (int i = 1; i <= n; i++) { a[i] = 1; for (int j = 1; j < i; j++) if (b[j] <= b[i]) a[i]++; for (int j = i + 1; j <= n; j++) if (b[j] < b[i]) a[i]++; } // 清空树状数组 for (int i = 0; i <= n + 1; i++) for (int j = 0; j <= n + 1; j++) bit_lin[i][j] = bit_col[i][j] = 0; // 初始状态:空子序列 update_lin(0, 0, 1); update_col(0, 0, 1); for (int i = 1; i <= n; i++) { int x = a[i]; vector<tuple<int, int, int>> buffs; // (val, lin, col) // 更新第 x 列:ways[m][x] += sum_{mp' < x} ways[m][mp'] for (int j = x + 1; j <= n; j++) { int val = query_lin(j, x - 1); if (val) buffs.push_back({val, j, x}); } // 更新第 x 行:ways[x][mp] += sum_{m' < x} ways[m'][mp] for (int q = 0; q < x; q++) { int val = query_col(q, x - 1); if (val) buffs.push_back({val, x, q}); } // 批量更新 for (auto [val, lin, col] : buffs) { update_lin(lin, col, val); update_col(col, lin, val); } } // 求和 int ans = 0; for (int lin = 0; lin <= n; lin++) addSelf(ans, query_lin(lin, n)); cout << ans << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) solve(); return 0; }
六、复杂度分析
- 离散化:(可优化至 ,但 不需要)。
- DP 转移:每轮枚举 个位置,每次查询/更新树状数组 。
- 总复杂度:,对于 完全可行。
- 空间复杂度:(两个二维树状数组)。
- 1
信息
- ID
- 6693
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者