[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;
}
posted @ 2024-05-11 11:08  Time_Limit_Exceeded  阅读(62)  评论(0编辑  收藏  举报