• P2258子矩阵


    传送

    一道看起来就很暴力的题。

    这道题不仅暴力,还要用正确的姿势打开暴力。

    因为子矩阵的参数有两个,一个行一个列(废话)

    我们一次枚举两个参数很容易乱对不对?所以我们先枚举行,再枚举列

    枚举完行,列,就计算一次当前子矩阵的分数,与ans取min。

    代码:

    但是复杂度会高到爆炸。来我们深吸一口O2

    // luogu-judger-enable-o2
    #include<bits/stdc++.h>
    using namespace std;
    int ma[20][20],n,m,r,c,ans=2147483647,ch[20],cl[20];
    void js()
    {  int an=0;
        for(int i=1;i<=r;i++)//求子矩阵左右的差的绝对值
            for(int j=2;j<=c;j++)
             an+=abs(ma[ch[i]][cl[j]]-ma[ch[i]][cl[j-1]]);
        for(int i=2;i<=r;i++)//求上下的差的绝对值
            for(int j=1;j<=c;j++)
             an+=abs(ma[ch[i]][cl[j]]-ma[ch[i-1]][cl[j]]);
        ans=min(ans,an);
    }
    void dfs(int x,int y,int nr,int nc)//x:当前枚举的行在原矩阵中是第x行,y:当前枚举的列在原矩阵中是第y列,nr:将要枚举的行的数量,nc:将要枚举的列的数量
    {
        if(nc==c+1)
        {
            js();
            return;
        }
        if((x>n&&nr!=r+1)||(y>m&&nc!=c+1))return ;
        if(nr==r+1)//当枚举完行了之后,枚举列
        {
            for(int i=y;i<=m;i++)
            {
                cl[nc]=i;
                dfs(x,i+1,nr,nc+1);
            }
        }
        else//枚举行
        {
            for(int i=x;i<=n;i++)
            {
                ch[nr]=i;
                dfs(i+1,y,nr+1,nc);
            }
        }
    }
    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",&ma[i][j]);
        dfs(1,1,1,1);
        printf("%d",ans);  
    }
    TTTLE

    还是T成这种美丽的图形

    你看它T的多有美感

    好了我们仔(ting)细(xue)思(zhang)考(jiang)怎么不吸氧通过剩下这几个点

    能(xue)想(zhang)到(shuo)用dp来解决这道题。

    但是这道题实在是太暴力了,所以我们还是得先暴力一下。

    我们依旧先枚举行。然后对列进行dp。

    设ver[i]为第i列的元素上下之差的绝对值之和,del[i][j]是第i列与第j列每行的元素之差的绝对值之和,d[i][j]为前i列,选择了j列,这j列中一定有第i列时的最小得分。

    简单的画一下ver和del

    ver:

    每一个元素减上面那个元素的差的绝对值加起来就是ver

    del:

    每一行右边减左边的差的绝对值加起来就是del

    我们考虑dp[i][j]的转移方程

    dp[i][j]可以由dp[i-1][j-1]再选上第i列得到,可以由dp[i-2][j-1]再选第i列得到,可以由dp[i-3][j-1]得到……

    所以状态转移方程就是dp[i][j]=min{dp[i][j],dp[i-k][j-1]+ver[i]+del[i-k][i]}(1<=k,i-k>=j-1)

    最终答案就是dp[i][c](c<=i<=m)的最小值

    #include<bits/stdc++.h>
    using namespace std;
    int ma[20][20],n,m,r,c,ans=2147483647,ver[20],del[20][20],d[20][20],ch[20];//ch是选择的行
    void dp()
    {
        memset(d,63,sizeof(d));//千万不要memset成0
        memset(ver,0,sizeof(ver));
        memset(del,0,sizeof(del));
        for(int i=1;i<=m;i++)//枚举第i列
         for(int j=2;j<=r;j++)//枚举行的编号 
          ver[i]+=abs(ma[ch[j]][i]-ma[ch[j-1]][i]);
        for(int i=1;i<=m;i++)//第i列
         for(int j=i+1;j<=m;j++)//第j列
          for(int k=1;k<=r;k++)//枚举行(注意循环的顺序)
           del[i][j]+=abs(ma[ch[k]][j]-ma[ch[k]][i]);  
        for(int i=1;i<=m;i++)
         d[i][1]=ver[i];
        for(int i=1;i<=m;i++)  
         for(int j=1;j<=c;j++) 
          for(int k=1;k<i&&i-k>=j-1;k++)
           d[i][j]=min(d[i][j],d[i-k][j-1]+ver[i]+del[i-k][i]);
        
        for(int i=c;i<=m;i++)
         ans=min(ans,d[i][c]);
    }
    void dfs(int x,int nr)//x,nr的含义与爆搜的含义相同
    {
    
        if(nr==r+1)
        {
          dp();
          return;
        }    
        if(x>n)return ;
        for(int i=x;i<=n;i++)
            {
                ch[nr]=i;
                dfs(i+1,nr+1);
            }
    }
    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",&ma[i][j]);
        dfs(1,1);
        printf("%d",ans);  
    }
  • 相关阅读:
    什么是shell
    shell种类
    centos7 安装JAVA (JDK 1.8) 并配置环境变量
    netcore liunx docker修改默认的Datetime format
    centos 7 安装 mysql 8.0.18
    centos 解决 mysql command not found
    centos 7 + Net Core 3.0 + Docker 配置说明(不含https)
    centos 7 安装golang1.13.5
    centos 7 安装git并配置ssh
    阿里云服务器 centos 7 安装postgresql 11
  • 原文地址:https://www.cnblogs.com/lcez56jsy/p/11127435.html
Copyright © 2020-2023  润新知