codevs1711/洛谷P1436棋盘分割(noi1999)

来源:互联网 发布:js创建一个对象的方法 编辑:程序博客网 时间:2024/06/06 16:59

这是一道神题…
虽然也可以算一道好题…

题目分析

注意,本篇涉及大量奇怪的数学推理,不适者谨入!
首先我们来分析方差。(x表示平均值)
s2=1n(xxi)2=1n(x2+x2i2xxi)=x2+1nx2i2xnxi
=x2+1nx2i2x2=1nx2ix2
而平均值是已知的嘛。棋盘的总权值除以分割的块数嘛。
所以我们只要求最小平方和嘛。
首先我们用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;}
原创粉丝点击