题解 P2258 【子矩阵】

题目大意

Solution [NOIP2014普及]子矩阵

题目大意:定义一个矩阵的分值为两两相邻元素的差的绝对值,试在一个(n)(m)列的矩阵中选出一个(r)(c)列的子矩阵(即行列交叉位置的元素),使其分值最小

动态规划,搜索


题目分析:一开始拿到这道题想的是爆搜,但是分析了一下时间复杂度(O(C_n^rC_m^c))怕是有点悬.找着题意(dp)?,四维(dp)写不出来在下告辞

然后就有一种神奇的做法,先(dfs)枚举行,然后就会得到很多(r)(m)列的矩阵,然后再用二维(dp)求解

(f[i][j])表示前(i)列,当前已经选了(j)列的最小分值.设(w[i])表示(i)这一列的分值,(v[i][j])表示(i,j)这两列的分值,那么

[f[i][j] = min{f[k][j - 1] + w[i] + v[k][i] quad | quad j - 1 leq k < i} ]

通过一些技巧来处理可以不用复杂的(if)语句来考虑边界条件,让程序更加简洁.即假设有一个虚拟的第(0)

分析一下复杂度?枚举行为(O(C_n^r)),进行一次(dp)的成本为(O(m^2))

所以总复杂度为(O(C_n^rm^2)),跑的过去(我常数有那么爆炸吗,吸口氧运行时间变成了原来的(frac{1}{4}))

代码奉上

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 32;
int n,m,r,c,p[maxn],w[maxn],v[maxn][maxn],f[maxn][maxn],val[maxn][maxn],ans = 1e9;
inline void dp(){//进行dp
    for(int i = 1;i <= m;i++)//初始化
        for(int j = 1;j <= m;j++)
            f[i][j] = 1e9;
    for(int i = 1;i <= m;i++){//计算每一列的分值
        w[i] = 0;
        for(int j = 2;j <= r;j++)
            w[i] += abs(val[p[j]][i] - val[p[j - 1]][i]);
    }
    for(int i = 1;i <= m;i++)//初始化
        for(int j = 1;j <= m;j++)
            v[i][j] = 0;
    for(int i = 1;i <= m;i++)//计算两两列之间的分值
        for(int j = 1;j <= m;j++)
            for(int k = 1;k <= r;k++)
                v[i][j] += abs(val[p[k]][i] - val[p[k]][j]);
    for(int i = 1;i <= m;i++)//进行dp
        for(int j = 1;j <= min(i,c);j++){
            for(int k = j - 1;k < i;k++)
                    f[i][j] = min(f[i][j],f[k][j - 1] + w[i] + v[k][i]);
            if(j == c)ans = min(ans,f[i][j]);//如果已经选了r行c列了,更新答案
        }
}
inline void dfs(int line,int step){//dfs枚举行
    if(step == r + 1){
        dp();
        return;
    }
    if(line > n)return;
    p[step] = line;
    dfs(line + 1,step + 1);
    dfs(line + 1,step);
}
int main(){
    scanf("%d %d %d %d",&n,&m,&r,&c);
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++)
            scanf("%d",&val[i][j]);
    dfs(1,1);
    printf("%d
",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/colazcy/p/11514983.html