题解:P5784 [CQOI2008] 矩阵的个数

· · 题解

背景

楼上的有些题解没有具体提到 dp 的状态转移这样优化是对的,以及为什么看似 TLE 的复杂度是对的。所以才诞生了这篇题解。

题意

给定一个 n×3 的矩阵的各行各列之和,求满足条件的矩阵的个数。

## 思路 这是一道技术题。这一类题主要有两种解决方式: - 数学,排列组合 - 计数型 dp。 而我们要发现这题的一个特点:只有 $3$ 列。极有限的行数提示我们使用 dp。因为使用 dp 的条件是状态有限。 ## 朴素 dp $a_i$ 表示第 $i$ 行的和。 $f_{i,c_1,c_2,c_3}$ 表示处理:前 $i$ 行,每列的和分别是 $c_1,c_2,c_3$ 的矩阵个数。 那么一个 dp 状态是合法的,当且仅当:$$c_1+c_2+c_3=\sum_{i=1}^na_i$$ 枚举在第 $i$ 行会填入的三个非负整数 $s_{i,1},s_{i,2},s_{i,3}$。要求:$$s_{i,1}+s_{i,2}+s_{i,3}=a_i$$ 状态转移方程 $$f_{i,c_1,c_2,c_3}=\sum f_{i-1,c_1-s_{i,1},c_2-s_{i,2},c_3-s_{i,3}}$$。 初始 $f_{0,0,0,0}$ 为 $1$,其余为 $0$。 目标 $f_{n,C_1,C_2,C_3}$。 ## 对 dp 优化 朴素 dp 复杂度 $\mathcal{O}(n\cdot V^6)$($V$ 表示值域),这是远远不能接受的。为了减少复杂度,我们要裁剪冗余状态和转移。 前面谈到 $c_1+c_2+c_3$ 为定值,$s_{i,1}+s_{i,2}+s_{i,3}$ 也是定值。所以我们在每次枚举 $c$ 和 $s$ 时有很多是不合法的状态。 因此我们只枚举 $c_1,c_2,s_{i,1},s_{i,2}$,当 $c_1,c_2,s_{i,1},s_{i,2}$ 确定时,$c_3,s_{i,3}$ 也随之确定了。 所以令 $f_{i,c_1,c_2}$ 表示处理:前 $i$ 行,每列的和分别是 $c_1,c_2$ 的矩阵个数。 状态转移方程 $f_{i,c_1,c_2}=\sum f_{i-1,c_1-s_{i,1},c_2-s_{i,2}}$。 初始 $f_{0,0,0}$ 为 $1$,其余为 $0$。 目标 $f_{n,C_1,C_2}$。 时间复杂度 $\mathcal{O}(n\cdot V^2\cdot (a_i^2))$。 空间复杂度 $\mathcal{O}(n\cdot V^2)$。 看似 TLE,实则这是可以接受的。因为: $$\begin{aligned} n\cdot V^2\cdot(a_i^2) \ &= V^2\cdot (\sum a_i^2) \\ &\le V^2\cdot(\sum a_i)^2 \\ &=V^2\cdot V^2=V^4 \end{aligned}$$ 其中 $V \le 125$,因此 $V^4 \approx 2\times 10^8$ 在可接受范围内,可过此题。 这其实这是一个小 trick,因为 $\sum a_i$ 与 $c_1+c_2+c_3$ 的量级是相等的。 ## Code ```cpp #include <bits/stdc++.h> using namespace std; #define int long long // 需要开 long long const int N = 210, M = 1e17; int n, c[10], a[N], f[N][N][N]; signed main() { cin >> n >> c[1] >> c[2] >> c[3]; int tot = 0; // tot 表示 a 数组的和 for (int i = 1;i <= n;i ++) cin >> a[i], tot += a[i]; if (tot != c[1] + c[2] + c[3]) return cout << 0, 0; // 检查总和是否匹配,不匹配直接输出0 f[0][0][0] = 1; // 初始化 for (int i = 1;i <= n;i ++) { for (int u = 0;u <= c[1];u ++) { for (int v = 0;v <= c[2];v ++) { // 第三列的值由 a[i]-s-t 确定 for (int s = 0;s <= a[i] && s <= u;s ++) { for (int t = 0;t + s <= a[i] && t <= v;t ++) { f[i][u][v] = (f[i][u][v] + f[i - 1][u - s][v - t]) % M;// 状态转移 } } } } } cout << f[n][c[1]][c[2]]; } ``` ### 总结 本题通过**减少状态维度**,利用约束条件优化了动态规划,将原本 $\mathcal{O}(nV^6)$ 的复杂度优化到 $\mathcal{O}(nV^4)$,在给定的数据范围内可以通过。关键在于发现行和与列和的**固定关系**,从而减少需要枚举的状态。 ::::info[AI 使用说明] 此题解在写作完成后使用 DeepSeek 进行了检查和润色。 ::::