这是一道神题…
虽然也可以算一道好题…
题目分析
注意,本篇涉及大量奇怪的数学推理,不适者谨入!
首先我们来分析方差。(x表示平均值)
s2=1n∗∑(x−xi)2=1n∗∑(x2+x2i−2∗x∗xi)=x2+1n∗∑x2i−2∗xn∗∑xi
=x2+1n∗∑x2i−2∗x2=1n∗∑x2i−x2
而平均值是已知的嘛。棋盘的总权值除以分割的块数嘛。
所以我们只要求最小平方和嘛。
首先我们用a[x][y]表示左上角一直到(x,y)的权值前缀和。
那么我们可以预处理出每一个矩形d[x1][y1][x2][y2] (左上角:(x1,y1),右下角:(x2,y2) )的权值和了,根据容斥随便搞一搞可以得到:
d[x1][y1][x2][y2]=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
然后我们就用f[i][x1][y1][x2][y2]表示以(x1,y1)为左上角,(x2,y2)为右下角的矩形切i刀的最小平方和,所以就有:
kl=min(f[0][x1][y1][j][y2]+f[i-1][j+1][y1][x2][y2],f[i-1][x1][y1][j][y2]+f[0][j+1][y1][x2][y2]);
kl=min(f[0][x1][y1][x2][j]+f[i-1][x1][j+1][x2][y2],f[i-1][x1][y1][x2][j]+f[0][x1][j+1][x2][y2]);
f[i][x1][y1][x2][y2]=kl;
分别表示横着切然后接着切下面那块,横着切然后接着切上面那块,竖着切然后接着切右边那块,竖着切然后接着切左边那块。
代码
#include<iostream>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int read(){
int q=0;char ch=' ';
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
return q;
}
int n;
int a[10][10],f[16][10][10][10][10];//a:矩阵前缀和
double ans,sum;
int main()
{
int i,j,x1,y1,x2,y2,kl;
n=read();
for(i=1;i<=8;i++)
for(j=1;j<=8;j++)
a[i][j]=read(),a[i][j]+=a[i][j-1];
for(i=2;i<=8;i++)
for(j=1;j<=8;j++)a[i][j]+=a[i-1][j];
for(x1=1;x1<=8;x1++)
for(y1=1;y1<=8;y1++)
for(x2=1;x2<=8;x2++)
for(y2=1;y2<=8;y2++){
kl=a[x2][y2]-a[x1-1][y2]-a[x2][y1-1]+a[x1-1][y1-1];
f[0][x1][y1][x2][y2]=kl*kl;
}
for(i=1;i<n;i++)//i代表切几刀
for(x1=1;x1<=8;x1++)
for(y1=1;y1<=8;y1++)
for(x2=1;x2<=8;x2++)
for(y2=1;y2<=8;y2++){
kl=999999999;
for(j=x1;j<x2;j++){//横着切
kl=min(kl,f[i-1][x1][y1][j][y2]+f[0][j+1][y1][x2][y2]);
kl=min(kl,f[0][x1][y1][j][y2]+f[i-1][j+1][y1][x2][y2]);
}
for(j=y1;j<y2;j++){//竖着切
kl=min(kl,f[i-1][x1][y1][x2][j]+f[0][x1][j+1][x2][y2]);
kl=min(kl,f[0][x1][y1][x2][j]+f[i-1][x1][j+1][x2][y2]);
}
f[i][x1][y1][x2][y2]=kl;
}
sum=(a[8][8]*1.0/n);sum=sum*sum;//注意0.1要放在a[8][8]的后面乘
sum=f[n-1][1][1][8][8]*1.0/n-sum;ans=sqrt(sum);
printf("%.3lf",ans);
return 0;
}