洛谷 P2258 子矩阵 题解

3 篇文章 0 订阅
2 篇文章 0 订阅

今天看了看枚举与搜索的题,意外中看到了一道“看似简单”的普及组题,然而事实是我想了好久,然后终于搞出来了。
题目:

题目描述

给出如下定义:

子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。
例如,下面左图中选取第2、4行和第2、4、5列交叉位置的元素得到一个2*3的子矩阵如右图所示。

9 3 3 3 9

9 4 8 7 4

1 7 4 6 6

6 8 5 6 9

7 4 5 6 1

的其中一个2*3的子矩阵是

4 7 4

8 6 9

相邻的元素:矩阵中的某个元素与其上下左右四个元素(如果存在的话)是相邻的。

矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。
本题任务:给定一个n行m列的正整数矩阵,请你从这个矩阵中选出一个r行c列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。

(本题目为2014NOIP普及T4)

输入输出格式

输入格式:
第一行包含用空格隔开的四个整数 nmrc 意义如问题描述中所述,每两个整数之间用一个空格隔开。

接下来的n行,每行包含m个用空格隔开的整数,用来表示问题描述中那个n行m列的矩阵。

输出格式:
输出共1行,包含1个整数,表示满足题目描述的子矩阵的最小分值。

输入输出样例

输入样例#1:
5 5 2 3
9 3 3 3 9
9 4 8 7 4
1 7 4 6 6
6 8 5 6 9
7 4 5 6 1
输出样例#1:
6
输入样例#2:
7 7 3 3
7 7 7 6 2 10 5
5 8 8 2 1 6 2
2 9 5 5 6 1 7
7 9 3 6 1 7 8
1 9 1 4 7 8 8
10 5 9 1 1 8 10
1 3 1 5 4 8 6
输出样例#2:
16

说明

【输入输出样例1说明】

该矩阵中分值最小的2行3列的子矩阵由原矩阵的第4行、第5行与第1列、第3列、第4列交叉位置的元素组成,为

6 5 6

7 5 6

,其分值为

|65|+|56|+|75|+|56|+|67|+|55|+|66|=6

【输入输出样例2说明】

该矩阵中分值最小的3行3列的子矩阵由原矩阵的第4行、第5行、第6行与第2列、第6列、第7列交叉位置的元素组成,选取的分值最小的子矩阵为

9 7 8 9 8 8 5 8 10

【数据说明】

对于50%的数据, 1n121m12 ,矩阵中的每个元素 1a[i][j]20

对于100%的数据, 1n161m16 ,矩阵中的每个元素 1a[i][j]1,000

1rn1cm

分析
最暴力的算法是枚举选择哪些行、列。复杂度为 O(C(n,r)×C(m,c)) 。不过显然不能承受。(C为组合数)
注意到虽然 O(C(n,r)×C(m,c)) 不能承受,但 O(C(n,r)) O(C(m,c)) 是可以接受的。
不妨考虑枚举其中一个(假设枚举行)。
枚举完行后,由于行已确定,因此可以把所有行捆绑,视为一个整体。
处理处列与列之间的价值,然后可以用动态规划解决这个问题。
dp[i][k] 表示前i列选了k列,并且第i列强制被选。那么转移方程为: dp[i][k]=dp[j][k1]+cost[j][i]+val[i] ,其中 j<icost[j][i] 表示第i列与第j列相邻的花费, val[i] 表示第i列内的花费。
答案即为 mindp[i][c]
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef double DB;

const int inf = 1e9 + 7;

int n, m, r, c;
int ans = inf;
int a[17][17], C[17], f[17][17], cost[17][17], val[17];

inline int read(){
    int r = 0, z = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') z = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){r = r * 10 + ch - '0'; ch = getchar();}
    return r * z;
}

int Max(int a, int b){return a > b ? a : b;}
int Min(int a, int b){return a < b ? a : b;}

int DP(){
    int res = inf;
    for(int i = 1; i <= m; i ++){
        val[i] = 0;
        for(int j = 1; j < r; j ++)
            val[i] += abs(a[C[j]][i] - a[C[j + 1]][i]);
    }
    memset(cost, 0, sizeof(cost));
    for(int i = 1; i <= m; i ++)
        for(int j = i + 1; j <= m; j ++){
            cost[i][j] = 0;
            for(int k = 1; k <= r; k ++)
                cost[i][j] += abs(a[C[k]][i] - a[C[k]][j]);
        }
    for(int i = 0; i <= m; i ++) f[i][0] = 0;
    for(int i = 1; i <= m; i ++){
        for(int k = 1; k <= Min(i, c); k ++){
            f[i][k] = inf;
            for(int j = k - 1; j < i; j ++)
                f[i][k] = Min(f[i][k], f[j][k - 1] + cost[j][i] + val[i]);
        }
    }
    for(int i = c; i <= m; i ++) res = Min(res, f[i][c]);
    return res;
}

void dfs(int u, int cnt){
    if(u > n){
        //cout<<1<<endl;
        if(cnt == r) ans = Min(ans, DP());
        return ;
    }
    dfs(u + 1, cnt);
    C[cnt + 1] = u;
    dfs(u + 1, cnt + 1);
}

void init(){
    n = read(), m = read(), r = read(), c = read();
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= m; j ++) a[i][j] = read();
    dfs(1, 0);
    printf("%d\n", ans);
}

int main(){
    init();
    return 0;   
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值