ybtoj & luoguP5752 棋盘分割(二维区间dp)
题意
给你一个 $8 * 8$ 的棋盘,每个格子上都有一个值,要求你切 $n - 1$ 刀,每一刀都在当前剩下的棋盘上切,也就是不能交叉切,然后得到 $n$ 个棋盘,规定棋盘的分值为该棋盘格子的点数之和,均方差为每个棋盘的分值减去 $8 * 8$ 棋盘的平均值的平方,均方差公式: $\sigma = \sqrt{ \frac{\sum_{i=1}^{n}({x_{i}-\bar{x})^{2}}}{n}}$ 其中 $x_{i}$ 就是划分出来的棋盘的分值。
分析
我们首先来看让方差最小等价于什么,因为肯定不能枚举方差对吧。把 $({x_{i}-\bar{x})^{2}}$ 这个式子给展开,其余的都是常数先忽略。我们得到的是一个完全平方式 $x_{i}^{2}-2x_{i}\bar{x}+\bar{x}^{2}$ 那么就可以看出来,有影响的只是这个 $x_{i}^{2}$ 所以我们求出他的最小值即可。
到此,我们可以引出区间动态规划了,不过是二维的,可以抽象理解一下。
状态是怎么样的呢?考虑考虑有哪些因素会对这个 $dp$ 有影响,首先区间 $dp$ 矩形的大小肯定是一个,还有什么?那自然是切了几刀。所以我们的状态就是 $f[x_{1}][y_{1}][x_{2}][y_{2}][k]$ 分别代表矩形的左上角 $(x_{1},y_{1})$ 右下角 $(x_{2},y_{2})$ 还有该矩形切的刀数 $k$
状态设计出来了,转移的方法就很好想了,一个矩形我们可以横着切,可以竖着切,这一刀会把原来的矩形分成两部分,一部分肯定是切了 $0$ 刀的,一部分肯定是切了 $k-1$ 刀的,我们只需要枚举切割的行列,记忆化搜索就行。至于求和,我们用二维前缀和来维护即可。
具体的细节请看代码来理解。
#include<cstdio> #include<queue> #include<iostream> #include<cstring> #include<algorithm> #include<cmath> #include<cctype> #include<vector> #include<string> using namespace std; double f[10][10][10][10][20]; double sum[10][10],ave; int a[10][10]; int n; inline double query(int x1,int y1,int x2,int y2){ double a = sum[x2][y2] - sum[x1-1][y2] - sum[x2][y1-1] + sum[x1-1][y1-1]; return (a-ave)*(a-ave);//二维前缀和访问顺便求个方差 } double ff(int x1,int y1,int x2,int y2,int k){ if(f[x1][y1][x2][y2][k])return f[x1][y1][x2][y2][k];//记忆化 if(k == 0)return query(x1,y1,x2,y2);//该矩形不切,就直接返回 (分值-平均数)^2 f[x1][y1][x2][y2][k] = 0x3f3f3f3f3f;//否则正无穷 for(int i=x1;i<x2;++i){//横着切,枚举切断的地方 f[x1][y1][x2][y2][k] = min(f[x1][y1][x2][y2][k],ff(x1,y1,i,y2,0)+ff(i+1,y1,x2,y2,k-1));//下半部分切k-1刀 f[x1][y1][x2][y2][k] = min(f[x1][y1][x2][y2][k],ff(x1,y1,i,y2,k-1)+ff(i+1,y1,x2,y2,0));//上半部分切k-1刀 } for(int j=y1;j<y2;++j){//竖着切 f[x1][y1][x2][y2][k] = min(f[x1][y1][x2][y2][k],ff(x1,y1,x2,j,0)+ff(x1,j+1,x2,y2,k-1));//右半部分切k-1刀 f[x1][y1][x2][y2][k] = min(f[x1][y1][x2][y2][k],ff(x1,y1,x2,j,k-1)+ff(x1,j+1,x2,y2,0));//左半部分切k-1刀(人类本质) } return f[x1][y1][x2][y2][k]; } signed main(){ scanf("%d",&n); for(int i=1;i<=8;++i){ for(int j=1;j<=8;++j)scanf("%d",&a[i][j]); } for(int i=1;i<=8;++i) for(int j=1;j<=8;++j) sum[i][j] = sum[i][j-1] + a[i][j]; for(int j=1;j<=8;++j) for(int i=1;i<=8;++i) sum[i][j] += sum[i-1][j];//前缀和处理 ave = sum[8][8] / n;//平均数 printf("%.3lf",sqrt(1.0*ff(1,1,8,8,n-1)/n)); }