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));
}

 

posted @ 2022-03-21 15:03  Xu_brezza  阅读(28)  评论(3编辑  收藏  举报