[洛谷P2258][NOIP2014PJ]子矩阵(dfs)(dp)

NOIP 2014普及组 T4(话说一道PJ组的题就把我卡了一个多小时诶)

这道题在我看第一次的时候是没有意识到这是一道DP题的,然后就摁着DFS敲了好长时间,结果敲了一个TLE

这是DP!!!

下面开始进入正题

 

题目描述

 

给出如下定义:

 

  1. 子矩阵:从一个矩阵当中选取某些行和某些列交叉位置所组成的新矩阵(保持行与列的相对顺序)被称为原矩阵的一个子矩阵。

 

例如,下面左图中选取第22、44行和第22、44、55列交叉位置的元素得到一个2 \times 32×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 \times 32×3的子矩阵是

 

4 7 4

 

8 6 9

 

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

  2. 矩阵的分值:矩阵中每一对相邻元素之差的绝对值之和。

 

本题任务:给定一个nn行mm列的正整数矩阵,请你从这个矩阵中选出一个rr行cc列的子矩阵,使得这个子矩阵的分值最小,并输出这个分值。

 

输入输出格式

输入格式:

 

第一行包含用空格隔开的四个整数n,m,r,cn,m,r,c,意义如问题描述中所述,每两个整数之间用一个空格隔开。

 

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

输出格式:

 

一个整数,表示满足题目描述的子矩阵的最小分值。

样例:
输入

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

输出

6

 

这道题就是将题目所给你的矩阵进行“选取”行与列的操作,从而得到所求的最大值的集合,但是重要的是我们不知道题目所给的行和列的值是多少,因此我们可以对此进行一个循环中的判断操作,在选出来这个行之后再去考虑列的情况,从而得出最优决策。

这个地方还有一个降维操作,就是我们在一个r * m(事先以及决定了选取那几行,该去考虑列的问题时)的矩阵中选取c列,这个时候我们可以把二维降低到一维,(因为这其中行已经有了判断的标准,我们只需要进行列的操作就足够了).

下面开始讲DP过程:

我们设f[i][j]表示在这个r*m的矩阵中,在其前i列中选择j列(且选的列中包括第i列),组成的子矩阵中,最小值(即其相邻元素的差的绝对值的和的最小值(之后的值等表达也是指的这个东西,即题目要求求出的值))是多少。

这样,推出状态转移方程如下:

f[i][j] = min (f[k][j-1] + hc[i][k] + lc[i])

下面就都是一些细节上的优化,代码里面已经讲的很清楚了。

Code:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,r,c,gs=1,minn=0x7fffffff,cmin;  //不可能选取0列 
int a[50][50];
int ch[50],hc[50][50],lc[50];  //hc[i][j] 对于第i列与第j列之间,所有同一行元素的差的绝对值的和
                               //lc[i]指的是第i列中所有元素的值
                               //ch[]是在dfs过程的数组 
int f[50][50];
void init()  //初始化 
{
    for(int i=1;i<=m;i++)
    {
        lc[i] = 0;
        for(int j=1;j<r;j++)
        lc[i] += abs(a[ch[j]][i] - a[ch[j + 1]][i]);  //注意是+= 
    }
    for(int i=2;i<=m;i++)
    {
        for(int j=1;j<i;j++)  //注意j<i 
        {
            hc[i][j] = 0;  //注意初始化 
            for(int k=1;k<=r;k++)
            hc[i][j] += abs(a[ch[k]][i] - a[ch[k]][j]);  //注意是+= 
        }
    }
}
void dp()
{
    for(int i=1;i<=m;i++)
    {
        cmin = min(i,c);
        for(int j=1;j<=cmin;j++)
        {
            if(j == 1)  //这个边界是只选取一列,就把元素赋进去就好 
            f[i][j] = lc[i];
            else
            if(j == i)  //这个边界是前i列都需要选取 
            f[i][j] = f[i - 1][j - 1] + lc[i] + hc[i][j - 1];
            else
            {
                f[i][j] = 0x7fffffff;  //注意初始化 
                for(int k=j-1;k<i;k++)
                f[i][j] = min(f[i][j],f[k][j - 1] + lc[i] + hc[i][k]);  //取最小值 
            }
            if(j == c)  //如果这种状态存在,那我们就更新一下 
            minn = min(minn,f[i][c]);
        }
    }
}
void dfs(int rt)
{
    if(rt > n)  // 
    {
        init();
        dp();
        return ;
    }
    if(r - gs + 1 == n - rt + 1)  //敲黑板!
    /*
    这个地方是一个剪枝,主要优化在了果rt和rt以后的元素必须全部取完,才能满足刚好有r个的条件,
    则必须rt,否则便会取到少于r个元素的情况。
    这样我们保证了rt > n时所有情况都刚好有r个,
    */
    {
        ch[gs++] = rt;  //注意是gs++而不是++gs 
        dfs(rt + 1);
        ch[gs--] = 0;  //注意是gs--而不是--gs 
        return ;
    }
    dfs(rt + 1);
    if(gs <= r)  //如果已经选取满了 
    {
        ch[gs++] = rt;  //但是还是需要这一步 
        dfs(rt + 1);
        ch[gs--] = 0;
    }
}
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",&a[i][j]);  //输入初始矩阵 
    dfs(1);  //开始搜索 
    printf("%d",minn);  //答案已经被更新,输出就行 
    return 0;
}

 

posted @ 2019-03-10 16:01  6954717  阅读(306)  评论(0编辑  收藏  举报