[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("\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(仅剪枝函数) ```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("\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相邻时的代价 */ ```\]

posted @ 2018-10-29 10:46  _onglu  阅读(334)  评论(0编辑  收藏  举报