[NOIP2014 普及组] 子矩阵 题解
[NOIP2014 普及组] 子矩阵
解法
暴力
先介绍比较暴力的解法,不难想出,我们可以直接暴力搜索每个子矩阵。对于每个子矩阵的贡献,我们求出最小的那个即可。
复杂度为 \(O(\tbinom{n}{r} \cdot \tbinom{m}{c}\cdot r \cdot c)\)
大概能得 60
分。
优化
考虑对暴力代码进行优化:注意到行与列的贡献是可以分开算的,二者之间并不会互相影响。所以我们可以先预处理出 \(sumR[i][j]\) 和 \(sumV[i][j]\) 数组,对于前者,表示对第 \(i\) 行,选择列状态为 \(j\) 时候的行贡献,后者同理。
复杂度为 \(O(\binom{n}{r} \cdot \binom{m}{c} \cdot \max(r, c))\)
大概能得 80
分。
正解
对于暴力的优化我自己已经想不出更好的了,所以考虑其他写法。
考虑本题的一维状态下的情况,即对于一个数组 \(a\) 来说,取 \(n\) 个值,问取出来相邻数之间的差的绝对值之和最小。对于这个问题,很容易想到用 \(dp\) 的方法去做。我们定义 \(dp[i][j]\) 表示前 \(i\) 个数选了 \(j\) 个(其中 \(i\) 是选了的)的最小贡献。转移为 \(dp[i][j] = min_{v < i} dp[v][j - 1] + |a[i] - a[v]|\)
注意到在本题中行与列之间的贡献不会相互影响,所以我们可以把一行(列)的状态看作一个整体后,转化为一维状态下的问题进行考虑。
令 \(dp[i][j][con]\) 表示前 \(i\) 行, 选了 \(j\) 个,选的列状态为 \(con\) 的情况下的最小贡献。那么我们转移为 \(dp[i][j][con] = min_{v} dp[v][j - 1][con] + sum[i][con] + d[v][i][con]\)
其中 \(sum[i][con]\) 表示第 \(i\) 行选择列的状态为 \(con\) 时的行贡献,\(d[v][i][con]\) 表示第 \(v\) 行和第 \(i\) 行,选择的状态为 \(con\) 的对应列元素之间的差绝对值。
复杂度为 \(O(\binom{m}{c} \cdot n ^ 2 \cdot r)\)
代码
#include <bits/stdc++.h>
#define endl '\n'
#define ls u << 1
#define rs u << 1 | 1
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f, N = 1e5 + 10;
const int MOD = 1e9 + 7;
const double eps = 1e-6;
const double PI = acos(-1);
inline int lowbit(int x) {return x & (-x);}
inline void solve() {
int n, m, r, c; cin >> n >> m >> r >> c;
vector<vector<int>> g(n, vector<int>(m));
vector<int> bg;
for (int i = 0; i < n; i ++ ) {
for (int j = 0; j < m; j ++ ) cin >> g[i][j];
}
for (int ite = 0; ite < (1 << m); ite ++ ) {
vector<int> tmp;
for (int j = 0; j < m; j ++ ) if (ite >> j & 1) tmp.push_back(j);
if ((int)tmp.size() != c) continue;
bg.push_back(ite);
}
int num = (int)bg.size();
vector<vector<LL>> sum(n, vector<LL>(num));
vector<vector<vector<LL>>> con(n, vector<vector<LL>>(n, vector<LL>(num)));
vector<vector<vector<LL>>> dp(n, vector<vector<LL>>(r + 1, vector<LL>(num, INF)));
for (int i = 0; i < n; i ++ ) {
for (int idx = 0; idx < num; idx ++ ) {
int ite = bg[idx];
vector<int> tmp;
for (int j = 0; j < m; j ++ ) if (ite >> j & 1) tmp.push_back(j);
for (int j = 0; j < c - 1; j ++ ) sum[i][idx] += abs(g[i][tmp[j + 1]] - g[i][tmp[j]]);
}
}
for (int i = 0; i < n; i ++ ) {
for (int j = i + 1; j < n; j ++ ) {
for (int idx = 0; idx < num; idx ++ ) {
int ite = bg[idx];
for (int k = 0; k < m; k ++ ) {
if (ite >> k & 1) con[i][j][idx] += abs(g[j][k] - g[i][k]);
}
}
}
}
for (int i = 0; i < n; i ++ ) {
for (int j = 1; j <= r; j ++ ) {
for (int idx = 0; idx < num; idx ++ ) {
if (j == 1) dp[i][j][idx] = sum[i][idx];
for (int v = 0; v < i; v ++ )
dp[i][j][idx] = min(dp[i][j][idx], dp[v][j - 1][idx] + sum[i][idx] + con[v][i][idx]);
}
}
}
LL ans = INF;
for (int i = 0; i < n; i ++ ) {
for (int idx = 0; idx < num; idx ++ ) {
ans = min(ans, dp[i][r][idx]);
}
}
cout << ans << endl;
}
signed main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
auto now = clock();
#endif
ios::sync_with_stdio(false), cin.tie(nullptr);
cout << fixed << setprecision(2);
int _ = 1;
// cin >> _;
while (_ -- )
solve();
#ifdef DEBUG
cout << "============================" << endl;
cout << "Program run for " << (clock() - now) / (double)CLOCKS_PER_SEC * 1000 << " ms." << endl;
#endif
return 0;
}