【题解】 P2258 子矩阵

在矩阵中求解最优情况,其中$n \leq 16$

假设现在已经得到保留的行与列的编号,递推矩阵分值的复杂度是$O(n^2)$遍历一遍就ok

50pts 假设行列全部枚举全排列的话,枚举次数在最坏情况下是$(C_{12}^6)^2=924^2$,在加上求分值的复杂度则总复杂度还是勉强可以接受的?

直接看100pts解法,仍然枚举一次行的全排列,复杂度是$O(C_m^c)$,这个时候我们得到的其实是一行数列,数列上的每个数有选择和不选择两种状态,选择每个数会增加分值,分别是:

 - 自身的分数(该列上下数字差值绝对值和)
 
 - 相对左侧(右侧)的分数,左侧和右侧的选择都会影响这种相对分数
 
 那么提前预处理出数列上每个数两两之间相对的分数和自身的分数,因为要枚举$m$的原因,所以复杂度是$O(n^3)$
 
 进行dp,设$f[i][l]$为已选择到数列上第$i$个数,已选择$l$个数的最大值,很明显$f[i][l]=min(f[k][l-1]+sum[k][i])$ 其中$k \subseteq[1,i-1]$,不需要太多优化,只要枚举$i,k,l$就可以在$O(n^3)$之内解决问题,该种方法总复杂度$O(C_m^c n^3)$可以通过$n=16$的数据


```
//2019/7/19->Riko->AtNCU->luoguP2258
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;

bool digit (int ch) { return (ch <= '9' and ch >= '0');}
inline int in () {
    int x = 0, ch = getchar(), base = 1;
    while (!digit(ch)) {
        if (base == '-') base = -1;
        ch = getchar();
    }
    while (digit(ch)) x = x*10+ch-'0', ch = getchar();
    return x*base;
}
template <typename T> inline void smin (T& x, T y) { if (x > y) x = y;}
inline int abs (int x) { return (x < 0) ? -x : x;}

const int N = 24;
int n, m, r, c, ans, cnt;
int f[N][N], a[N][N], sum[N][N], suml[N], bol[N];

void work () {
    memset(sum, 0, sizeof(sum));
    memset(suml, 0, sizeof(suml));
    memset(f, 0x3f, sizeof(f));
    for (int i = 1; i <= n; ++i) {
        for (int j = i+1; j <= n; ++j) {
            int seg = 0;
            for (int l = 1; l <= m; ++l) {
                if (bol[l]) seg += abs(a[i][l]-a[j][l]);
            }
            sum[i][j] = sum[j][i] = seg;
        }
        int last = 0;
        for (int l = 1; l <= m; ++l) {
            if (bol[l]) {
                if (last) suml[i] += abs(a[i][l]-last);
                last = a[i][l];
            }
        }
    }
    for (int i = 1; i <= n; ++i) { f[i][1] = suml[i];}
    for (int i = 1; i <= n; ++i) {
        for (int l = 2; l <= r; ++l) {
            int Min = f[0][0];
            for (int j = 1; j < i; ++j) {
                smin(Min, f[j][l-1]+sum[j][i]);
            }
            f[i][l] = Min+suml[i];
        }
    }
    for (int i = 1; i <= n; ++i) { smin(ans, f[i][r]);}
}
void dfs (int idx, int num) {
    if (num == c) {
        work();
        return;
    }
    for (int i = idx; i <= m; ++i) {
        bol[i] = true;
        dfs(i+1, num+1);
        bol[i] = false;
    }
}
void prepare () {
    n = in(); m = in();
    r = in(); c = in();
    ans = 999999999;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            a[i][j] = in();
        }
    }
    dfs(1, 0);
    printf("%d", ans);
} int main () { prepare();}
```

转载于:https://www.cnblogs.com/NHDR233/p/11246712.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值