CSP历年复赛题-P2258 [NOIP2014 普及组] 子矩阵

原题链接:https://www.luogu.com.cn/problem/P2258

题意解读:从矩阵中挑选出子矩阵(行、列都不一定连续),计算相邻元素差的绝对值之和最小值。

解题思路:

1、DFS+DFS

看到题目之后,直觉上应该是一个DP问题,但是考试中不一定能想到DP转移方程,不要放弃,可以使用暴力枚举来得到部分分

主要思路就是通过dfs先从n行中挑选r行,再用一个dfs从m列中挑选c列,计算每一次所选出的r行c列的值,取最小即可

时间复杂度:2^16 * 2^16 * 16^2,高达10^12,

70分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 20;
int n, m, r, c;
int w[N][N];
int ans = 1e9;
int rows[N], cols[N]; //dfs选择的行、列

void dfs_c(int k)
{
    if(k > c)
    {
        //计算rows、cols里存的行、列对应子矩阵的相邻元素差的绝对值之和
        int res = 0;
        //计算横向相邻的值
        for(int i = 1; i <= r; i++)
            for(int j = 2; j <= c; j++)
                res += abs(w[rows[i]][cols[j-1]] - w[rows[i]][cols[j]]);

        //计算纵向相邻的值
        for(int i = 1; i <= c; i++)
            for(int j = 2; j <= r; j++)
                res += abs(w[rows[j-1]][cols[i]] - w[rows[j]][cols[i]]);

        ans = min(ans, res);    
        return;
    }
    int start = 1;
    if(k > 1) start = cols[k-1] + 1; //从上一个列号+1开始选
    for(int i = start; i <= m; i++)
    {
        cols[k] = i;
        dfs_c(k + 1);
    }
}

void dfs_r(int k)
{
    if(k > r)
    {
        dfs_c(1);
        return;
    }
    int start = 1;
    if(k > 1) start = rows[k-1] + 1; //从上一个行号+1开始选
    for(int i = start; i <= n; i++)
    {
        rows[k] = i;
        dfs_r(k + 1);
    }

}

int main()
{
    cin >> n >> m >> r >> c;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> w[i][j];

    dfs_r(1);
    cout << ans;

    return 0;
}

2、DFS+DP

如何用DP来解决本题?

我们可以先简化一下问题:假如只有一行,要在一行中挑选一个子序列,使得相邻元素之差的绝对值之和最小

该问题就非常类似于最长上升子序列的模型!

可以设f[i][j]表示前i个元素选j个,且以i结尾的子序列的相邻元素之差的绝对值之和最小值

设cw[i][j]为第i个元素与第i个元素之差的绝对值,可以提前预计算出来

则不难想到递推公式:f[i][j] = min(f[i][j], f[k][j-1] + cw[k][i]),k从1~i-1,也就是看前j-1个子序列的结尾k在哪里,然后取最小值

本题是要选择r行、c列,那么可以先通过dfs枚举选择r行,再基于这r行利用dp来计算选择c列的最佳方案

设对于dfs枚举选中的r行中,

cw[i]表示所有r行第i列产生的纵向代价(代价:相邻元素之差的绝对值之和),rw[i][j]表示所有r行的第i列与第j列产生的横向代价

f[i][j]表示所有r行中前i列选择j列且以i列结尾的最小代价,则可以推出递推公式

f[i][j] = min(f[i][j], f[k][j-1] + cw[i] + rw[k][i]),k从1~i-1

初始化时:f[][]要设置为无穷大,对于f[i][1] = cw[i]

通过对行进行dfs,对列进行dp,就可以解决此题。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 20;
int n, m, r, c;
int w[N][N]; //原始矩阵
int rows[N]; //dfs选择的行
int cw[N]; //cw[i]表示dfs枚举出的一组r行第i列产生的纵向代价
int rw[N][N]; //rw[i][j]表示dfs枚举出的一组r行第i列与第j列产生的横向代价
int f[N][N]; //f[i][j]表示所有r行中前i列选择j列且以i列结尾的最小代价
int ans = 2e9;

void dp()
{
    //计算cw
    for(int i = 1; i <= m; i++) //每一列
    {
        int res = 0;
        for(int j = 2; j <= r; j++) //每一行
        {
            res += abs(w[rows[j]][i] - w[rows[j-1]][i]);
        }
        cw[i] = res;
    }

    //计算rw
    for(int i = 1; i < m; i++)
    {
        for(int j = i + 1; j <= m; j++) // 枚举所有不同列的组合
        {
            int res = 0;
            for(int k = 1; k <= r; k++) //每一行
            {
                res += abs(w[rows[k]][i] - w[rows[k]][j]);
            }
            rw[i][j] = res;
        }
    }

    //dp过程
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i <= m; i++)
    {
        f[i][1] = cw[i];
        for(int j = 2; j <= c; j++)
        {
            for(int k = 1; k <= i - 1; k++)
            {
                f[i][j] = min(f[i][j], f[k][j-1] + cw[i] + rw[k][i]);
            }
        }
        ans = min(ans, f[i][c]);
        //cout << f[i][c] << endl;
    }
}

void dfs_r(int k)
{
    if(k > r)
    {
        dp();
        return;
    }
    int start = 1;
    if(k > 1) start = rows[k-1] + 1; //从上一个行号+1开始选
    for(int i = start; i <= n; i++)
    {
        rows[k] = i;
        dfs_r(k + 1);
    }

}

int main()
{
    cin >> n >> m >> r >> c;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            cin >> w[i][j];

    dfs_r(1);
    cout << ans;

    return 0;
}

 

posted @ 2024-06-05 09:43  五月江城  阅读(136)  评论(0编辑  收藏  举报