[luogu]P2258 子矩阵

原题链接 :P2258 子矩阵

分析

题意很简单,定义一个矩阵的分值为所有相邻元素差的绝对值(每一对只计算一次)。
然后要求一个矩阵的分值最小子矩阵。
这里的子矩阵指选取r行,c列,交点组成的子矩阵。

50pts

普及题还是很良心的,部分分都给的很充足,打个爆搜50分就到手了。

55pts

爆搜加个最优性剪枝,能多5分orz。

100pts

矩阵不就考个dp吗……
不过打死也想不出状态转移方程,最后看了题解才知道。
首先要明确,dp不是指那种整个程序只有一个状态转移方程,然后forforfor就可以了的。
这里在dp之前我们可以通过枚举枚举掉行,然后再列上跑dp,状态转移方程就很好写了。
以下均为已经枚举好行之后的处理,默认行选择1,2,3行
\(v[i]\)表示第i列纵向相邻差的绝对值和。
\(g[i][j]\)表示第i行和第j行相邻时的绝对值和。
\(f[i][j]\)表示现在需要选取第i列,包括这一列一共选了j列,最小的价值。
状态转移方程就很明确了。
$\(f[i][j]=\)min{f[i][j],min{f[i-k][j-1]+v[i]+g[i-k][j]}}$
那么我们直接跑一边dp,就能求出当前最小值了。
然后答案就是所有最小值的最小值,直接跑个min就可以了。
详情见代码。

代码

50pts
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;
}
int n,m,r,c,f[109][3],a[19][19];
int ans=0x7fffffff;
void check(){
    int now=0;
    for(int i=1;i<=r;i++)
        for(int j=2;j<=c;j++)
            now+=abs(a[f[i][1]][f[j][2]]-a[f[i][1]][f[j-1][2]]);
    for(int i=2;i<=r;i++)
        for(int j=1;j<=c;j++)
            now+=abs(a[f[i][1]][f[j][2]]-a[f[i-1][1]][f[j][2]]);
    /*if(now==10){
        for(int i=1;i<=r;i++)printf("%d ",f[i][1]);printf("\n");
        for(int i=1;i<=c;i++)printf("%d ",f[i][2]);printf("\n");printf("\n");
    }*/
    ans=min(ans,now);
}
void dfs(int x,int y,int nr,int nc){
    //if(f[1][1]==4&&f[2][1]==5&&f[1][2]==1&&f[2][2]==3&&f[3][2]==4)printf("1111\n");
    if(nc==c+1){check();return ;}
    if((x>n&&nr!=r+1)||(y>m&&nc!=c+1))return ;
    //printf("%d %d %d %d\n",x,y,nr,nc);
    if(nr==r+1){
        for(int i=y;i<=m;i++){
            f[nc][2]=i;
            dfs(x,i+1,nr,nc+1);
        }
        return ;
    }else {
        for(int i=x;i<=n;i++){
            f[nr][1]=i;
            dfs(i+1,y,nr+1,nc);
        }
    }
}
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;
}
/* 发现n,m都是16
 * 感觉可以支持O(n^4)的算法 
 *  
 */
55pts(仅剪枝函数)
bool Prune(int nr,int nc){
  int now=0;
  for(int i=1;i<nr;i++)
      for(int j=2;j<nc;j++)
          now+=abs(a[f[i][1]][f[j][2]]-a[f[i][1]][f[j-1][2]]);
  for(int i=2;i<nr;i++)
      for(int j=1;j<nc;j++)
          now+=abs(a[f[i][1]][f[j][2]]-a[f[i-1][1]][f[j][2]]);
  if(now>ans)return true;
  else return false;
}
100pts
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;
}
int n,m,r,c,a[19][19];
int line[19],f[19][19];
int v[19],g[19][19];
int ans=0x7fffffff;
void dp(){
    memset(v,0,sizeof(v));
    memset(g,0,sizeof(g));
    memset(f,0x3f,sizeof(f));
    for(int i=1;i<=m;i++)
        for(int j=2;j<=r;j++)
            v[i]+=abs(a[line[j]][i]-a[line[j-1]][i]);
    for(int i=1;i<=m;i++)
        for(int j=1;j<i;j++)
            for(int k=1;k<=r;k++)
                g[j][i]=g[i][j]=g[i][j]+abs(a[line[k]][j]-a[line[k]][i]);
    /*for(int i=1;i<=m;i++){
        for(int j=1;j<=m;j++){
            printf("%d ",g[i][j]);
        }
        printf("\n");
    }*/
    for(int i=1;i<=m;i++)f[i][1]=v[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++)
                f[i][j]=min(f[i][j],f[i-k][j-1]+v[i]+g[i-k][i]);
    for(int i=c;i<=m;i++)
        ans=min(ans,f[i][c]);
}
void dfs(int x,int d){
    if(x==r+1){dp();return ;}
    if(d>n)return ;
    for(int i=d;i<=n;i++)line[x]=i,dfs(x+1,i+1);
}
int main()
{
    //freopen("data.in","r",stdin);
    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();
    line[1]=1;line[2]=2;line[3]=5;
    dfs(1,1);
    printf("%d\n",ans);
    return 0;
}
/* 
 * 先枚举行数,然后再已有行数的基础上进行dp求列数。
 * f[i][j]表示前i行,已用j列,取最后一列,最小价值。
 * f[i][j]=min(f[i][j],min{f[i-k][j-1]+v[i]+g[i-k][i]});
 * 预处理v[i]表示当前选i行的代价
 * g[i][j]表示i与j相邻时的代价 
 */

转载于:https://www.cnblogs.com/onglublog/p/9869326.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值