zoukankan      html  css  js  c++  java
  • [洛谷P2258][NOIP2014PJ]子矩阵(dfs)(dp)

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

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

    这是DP!!!

    下面开始进入正题

    题目描述

    给出如下定义:

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

    例如,下面左图中选取第22、44行和第22、44、55列交叉位置的元素得到一个2 imes 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 imes 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;
    }
  • 相关阅读:
    jenkins实现git自动拉取代码时替换配置文件
    【一些小常识】Linux文件目录的通配符用法/*
    mysql使用——sql实现随机取一条数据
    jmeter使用问题——数据库无法连接
    【测试点】微信小程序的常见测试点
    分位数介绍
    elk开源版本支持的功能
    kali下的webshell工具-Weevely
    kali中的webshell工具--webacoo
    sql报错注入:extractvalue、updatexml报错原理
  • 原文地址:https://www.cnblogs.com/lyp-Bird/p/10505639.html
Copyright © 2011-2022 走看看