【DP】【统计】【NOI1999】棋盘分割

这是一个关于8x8棋盘分割的问题,要求通过DP方法解决。输入包含一个整数n和棋盘上各格子的分值,输出为分割后的棋盘块的平均分值,精确到小数点后三位。题解涉及暴力搜索和记忆化搜索,以及均方差公式的变形。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了n-1次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)

分割

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成 n 块矩形棋盘,并使各矩形棋盘总分的均方差最小。
均方差σ=ni=1(xix¯)2n,其中平均值 x¯=ni=1xin xi 为第 i 块矩形棋盘的总分。
请编程对给出的棋盘及n,求出 σ 的最小值。

输入

第1行为一个整数 n(1<n<15)
第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。

输出

仅一个数,为 σ (四舍五入精确到小数点后三位)。

样例输入

3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3

样例输出

1.633


这道题乍一看数据范围就是DP。【暴力搜索确实超一点】
别的人的题解已经介绍的比较全了,这里说一下那个均方差公式的变形,否则是不能用别的题解所给的状态转移方程的。

σ=ni=1(xix¯)2n=ni=1(x2i2xix¯+x¯2)n=ni=1x2i2x¯ni=1xi+nx¯2n=ni=1x2inx¯2

这样的话我们只需要对每个棋盘上的矩形区域DP其各子块分值的平方和即可。
dp[y1][x1][y2][x2][k] 表示切了k次后左上角坐标为 (y1,x1) ,右下角坐标为 (y2,x2) 的矩形当前 ki=1x2i 的最小值。
则有四种转移始状态,为当前矩形为从某(上/下/左/右)侧切一整块,另一侧为 k1 个分割完的块。
直接上代码:

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int INF=1<<30;
int n, chess[9][9]={0}, sum[9][9]={0}, dp[9][9][9][9][15]={0};
//直接计算矩形(y1, x1)(y2, x2)矩形分数平方
int getX(int y1, int x1, int y2, int x2){
    int a=sum[y2][x2]-sum[y2][x1-1]-sum[y1-1][x2]+sum[y1-1][x1-1];
    return a*a;
}
int main(){
    scanf("%d", &n);
    //统一i表示y,j表示x
    for(int i=1;i<=8;i++)
        for(int j=1;j<=8;j++)
            scanf("%d", &chess[i][j]);
    //计算sum数组(矩形(1, 1)(i, j)的分数和),方便直接计算getX
    for(int i=1;i<=8;i++){
        for(int j=1;j<=8;j++)
            sum[i][j]=sum[i][j-1]+chess[i][j];
        for(int j=1;j<=8;j++)
            sum[i][j]+=sum[i-1][j];
    }
    //初值
    for(int i1=1;i1<=8;i1++)
        for(int j1=1;j1<=8;j1++)
            for(int i2=i1;i2<=8;i2++)
                for(int j2=j1;j2<=8;j2++)
                    dp[i1][j1][i2][j2][0]=getX(i1, j1, i2, j2);
    //这里的i是切割数(分析里的k)
    for(int i=1;i<n;i++)
        for(int i1=1;i1<=8;i1++)
            for(int j1=1;j1<=8;j1++)
                for(int i2=i1;i2<=8;i2++)
                    for(int j2=j1;j2<=8;j2++){
                        //赋值INF,若状态不合法不会干扰其他状态
                        dp[i1][j1][i2][j2][i]=INF;
                        //左右切割
                        for(int k=j1;k<j2;k++)
                            dp[i1][j1][i2][j2][i]=min(dp[i1][j1][i2][j2][i], min(dp[i1][j1][i2][k][i-1]+dp[i1][k+1][i2][j2][0], dp[i1][j1][i2][k][0]+dp[i1][k+1][i2][j2][i-1]));
                        //上下切割
                        for(int k=i1;k<i2;k++)
                            dp[i1][j1][i2][j2][i]=min(dp[i1][j1][i2][j2][i], min(dp[i1][j1][k][j2][i-1]+dp[k+1][j1][i2][j2][0], dp[i1][j1][k][j2][0]+dp[k+1][j1][i2][j2][i-1]));
                    }
    //套公式
    printf("%.3f\n", sqrt(double(dp[1][1][8][8][n-1])/n-double(sum[8][8]*sum[8][8])/n/n));
    return 0;
}

复杂度真吓人。记忆化搜索亦可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值