• [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 ```cpp #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(" "); for(int i=1;i<=c;i++)printf("%d ",f[i][2]);printf(" ");printf(" "); }*/ 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 "); if(nc==c+1){check();return ;} if((x>n&&nr!=r+1)||(y>m&&nc!=c+1))return ; //printf("%d %d %d %d ",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 ",ans); return 0; } /* 发现n,m都是16 * 感觉可以支持O(n^4)的算法 * */ ``` #### 55pts(仅剪枝函数) ```cpp 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 ```cpp #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(" "); }*/ 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 ",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相邻时的代价 */ ```]

  • 相关阅读:
    JasperReport
    iconv
    Groovy
    Groovy
    file
    PowerShell 自动合并 db first 的dbcontext
    获取存储过程的名称和信息
    傻瓜式同步svn到git
    强大的生成pdf,word,等等文档工具
    iphone x 底部横条适配
  • 原文地址:https://www.cnblogs.com/onglublog/p/9869326.html
Copyright © 2020-2023  润新知