P2258 子矩阵

P2258 子矩阵

 题解

 这题的标签足以概括这道nice题目了

就是考虑暴力枚举 ---> 先枚举行再枚举列

----->dp优化

then  AC

注释加在代码里了

 

55'暴力代码

//55'暴力
 
#include<bits/stdc++.h>

using namespace std;

int n,m,r,c;
int a[20][20];
int dph[20],dpl[20];
//dph表示子矩阵的每一行的编号,dpl表示子矩阵的每一列的编号 
int ans=0x7fffffff;

inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

void DP()
{
    int now=0;  //当前得到一个完整的子矩阵,计算他的分数 
    for(int i=1;i<=r;i++)
      for(int j=2;j<=c;j++)
        now+=abs(a[dph[i]][dpl[j]]-a[dph[i]][dpl[j-1]]);
    for(int i=2;i<=r;i++)
      for(int j=1;j<=c;j++)
        now+=abs(a[dph[i]][dpl[j]]-a[dph[i-1]][dpl[j]]);
    ans=min(ans,now);
} 

void dfs(int x,int y,int rr,int cc)
//当前走到了第x行,第y列,子矩阵已经枚举了rr行,cc列 
{
    if(cc==c+1) return DP(); 
    //我们先枚举行,列枚举完了那么行一定也枚举完了,所以拿去跑一个ans 
    if((x>n&&rr!=r+1)||(y>m&&cc!=c+1)) return;
    //不合法情况直接退出 
    if(rr==r+1)  //行已经枚举够了 
    {
        for(int i=y;i<=m;i++)  //枚举列 
        {
            dpl[cc]=i;  //记录列 
            dfs(x,i+1,rr,cc+1);  //继续往下递归 
        }

    }
    else
    {
        for(int i=x;i<=n;i++)  //继续枚举行 
        {
            dph[rr]=i;  //记录行 
            dfs(i+1,y,rr+1,cc);  //继续往下递归 
        } 

    }
}

int main()
{
    n=read();m=read();r=read();c=read();
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
         a[i][j]=read();

    dfs(1,1,1,1);
    
    printf("%d\n",ans);
     
    return 0;
}

 

 

 

100‘ AC代码

#include<bits/stdc++.h>

using namespace std;

int n,m,r,c;
int a[20][20];
int line[20],dp[20][20]; 
//dp[i][j]表示前i列,已用j列得到的最小价值
int updo[20],leri[20][20];
//updo[i]表示对于i列的上下绝对值差的和,leri[i][j]表示i列和j列左右差的和 
int ans=0x7fffffff;

inline int read()
{
    int ans=0;
    char last=' ',ch=getchar();
    while(ch<'0'||ch>'9') last=ch,ch=getchar();
    while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar();
    if(last=='-') ans=-ans;
    return ans;
}

//转移方程 dp[i][j] = min(dp[i][j],dp[i-k][j-1]+ver[i]+del[i-k][i]}); 
void DP()
{
    memset(dp,63,sizeof(dp));
    memset(updo,0,sizeof(updo));
    memset(leri,0,sizeof(leri));
    
    for(int i=1;i<=m;i++) //枚举每一列i
      for(int j=2;j<=r;j++)
      updo[i]+=abs(a[line[j]][i]-a[line[j-1]][i]);//第i列的上下差值 
    
    for(int i=1;i<=m;i++)  //枚举每一列 
      for(int j=i+1;j<=m;j++)//枚举列,联系i,找差值 
         for(int k=1;k<=r;k++)//枚举子矩阵每一行 
         leri[i][j]+=abs(a[line[k]][j]-a[line[k]][i]);
    
    for(int i=1;i<=m;i++)
      dp[i][1]=updo[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++)
         //k一定要小于i(不然木得减了),i-k一定要大于等于j-1,因为j-1是从i-k行的当中挑出来的 
           dp[i][j]=min(dp[i][j],dp[i-k][j-1]+updo[i]+leri[i-k][i]);
            //dp取最小,比较(1),(2)
            //(1)dp[i][j]也就是上一轮循环到的自己
            //(2)从前i-k行中选j-1列,然后再选上当前这一列 
         
    for(int i=c;i<=m;i++)  //从c行后取最小值 
      ans=min(ans,dp[i][c]);
}

void dfs(int now ,int x)
//子矩阵已经枚举了now行,当前走到了第x行 
{
    if(now==r+1) return DP(); //已经枚举够行了 
    if(x>=n+1) return ;  //不合法直接退出 
    
    for(int i=x;i<=n;i++) //继续递归子矩阵的行 
    {
        line[now]=i;
        dfs(now+1,i+1);
    }
    
}

int main()
{
    n=read();m=read();r=read();c=read();
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
         a[i][j]=read();

    dfs(1,1);
    
    printf("%d\n",ans);
     
    return 0;
}

难得一见

某谷又炸了

 

posted @ 2019-07-03 16:47  晔子  阅读(171)  评论(0编辑  收藏  举报