题意
给出一个\(n*m\)的\(0,1\)矩阵,若一个矩阵中的所有元素都相同,则这个矩阵的代价为\(0\),如果不是则选择一种将它分成两个子矩阵的方案,代价为所有方案中(两个子矩阵的代价的较大值+\(1\))的最小值。
\(n,m \leq 185\)
传送门
思路
\(dp[ i ][ j ][ k ][ l ]\) 表示左上角是 $( i , j ) $ 、右下角是 $ ( k , l ) $的矩阵的最小代价,四维扛不住。
因为如果每次从中间分,\(log(n)+log(m)\)次就变成\(1*1\)的格子,代价是 \(0\),所以答案最多是\(log(n)+log(m)\)。
因此可以将值放进状态中,$dp[ i ][ j1 ][ j2 ][ k ] $表示左上角是 \(( i , j1 )\)、右上角是 $ ( i , j2 ) \(、用\) k $的代价,往下最长能延伸到哪行。
转移的时候考虑横着切与竖着切。令$ d = dp[ i ][ j1 ][ j2 ][ k ]$ :
横着切:$ d = dp \space [ dp[ i ][ j1 ][ j2 ][ k-1 ] +1] \space [ j1 ][ j2 ][ k-1 ] $;
竖着切:\([ j1 , j3 ] 和 [ j3+1 , j2 ]\) 就是分出的两部分;
\(dp[ i ][ j1 ][ j3 ][ k-1 ]\) 和 \(dp[ i ][ j3+1 ][ j2 ][ k-1 ]\)的最小值是答案,\(d\)为最小值中的最大值。至此我们得出了\(O(m)\)的暴力转移。
因为\(dp\)值明显随矩阵增大而减小,所以可二分\(j3\)。考虑二分找出最大的\(min( dp[ i ][ j1 ][ j3 ][ k-1 ] , dp[ i ][ j3+1 ][ j2 ][ k-1 ] )\)。(左比右大往左,反过来,总之越接近越好)
初始化出所有\(k=0\)的情况即可
#include <bits/stdc++.h> const int N=190; using std::max; using std::min; int n,m,a[N][N],sum[N][N],dp[N][N][N][15]; char c[N][N]; int main(){ scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%s",c[i]+1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if (c[i][j]=='.') a[i][j]=1; else a[i][j]=0; sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j]; } for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) for (int k=j;k<=m;k++){ int l=i,r=n,ans=i-1; while (l<=r){ int mid=(l+r)>>1; int s=sum[mid][k]-sum[mid][j-1]-sum[i-1][k]+sum[i-1][j-1]; if (s==0 || s==(mid-i+1)*(k-j+1)) ans=mid,l=mid+1; else r=mid-1; } dp[i][j][k][0]=ans; } int k=1; for (k=1;dp[1][1][m][k-1]<n;k++) for (int h=1;h<=m;h++) for (int i=1;i<=n;i++) for (int j1=1,j2=j1+h-1;j2<=m;j1++,j2++){ if (dp[i][j1][j2][k-1]==n){ dp[i][j1][j2][k]=n; continue; } dp[i][j1][j2][k]=dp[dp[i][j1][j2][k-1]+1][j1][j2][k-1];//横 if(dp[i][j1][j2][k]==n) continue; int l=j1,r=j2-1,ans=0;//竖 while (l<=r){ int mid=(l+r)>>1; int x=dp[i][j1][mid][k-1],y=dp[i][mid+1][j2][k-1]; ans=max(ans,min(x,y)); if (x<y) r=mid-1; else l=mid+1; } dp[i][j1][j2][k]=max(dp[i][j1][j2][k],ans); } printf("%d\n",k-1); }