题目描述
将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了n-1次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。 (每次切割都只能沿着棋盘格子的边进行)
原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。求出均方差的最小值。
其中,均方差的定义为:
(里面的括号应该还有一个平方,不然固输0岂不美哉)
数据范围
1<n<15,所有数均为不大于100的非负整数
解析
分析题面可以发现一些结论:
1.只要大矩阵与n确定,平均值就是可以算出的定值
2.每次分完后只能对其中的一部分继续切割
对于第2条举个例子:n=4时,田字型的切割就是不合法的,这对于本题至关重要
我们可以先算出均方差根号里面那个西格玛加和的总值的最小值,最后再除n开根号
定义dp[x1][y1][x2][y2][k]:左上角为x1,y1,右下角为x2,y2还可以切k刀时,切成的k+1部分西格玛方差的最小值
显然,k=0时:
if(k==0) return abs((double)he(x1,y1,x2,y2)-ave)*abs((double)he(x1,y1,x2,y2)-ave);
ave为提前可算好的平均值,he函数可以用二维前缀和O(1)求出,具体见代码(其实遍历求本题时间可能也爆不了。。。 )
对于转移,我们枚举切出去的那个不能再切的矩形的位置即可
代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
#include <queue>
#include <string>
#include<map>
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a));
#define ull unsigned ll
using namespace std;
const int N=15;
int m,n,tot;
int mp[10][10],sum[10][10];
double ave;
double dp[N][N][N][N][N];
int he(int x1,int y1,int x2,int y2){
return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
double find(int x1,int y1,int x2,int y2,int k){
if(dp[x1][y1][x2][y2][k]) return dp[x1][y1][x2][y2][k];
if(k==0) return abs((double)he(x1,y1,x2,y2)-ave)*abs((double)he(x1,y1,x2,y2)-ave);
dp[x1][y1][x2][y2][k]=1e9;
for(int i=x1;i<x2;i++){//横着切
dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,i,y2,0)+find(i+1,y1,x2,y2,k-1));
dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,i,y2,k-1)+find(i+1,y1,x2,y2,0));
//注意这里还要反着枚举一遍,因为不能切的部分可能在下边
}
for(int j=y1;j<y2;j++){//竖着切
dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,x2,j,0)+find(x1,j+1,x2,y2,k-1));
dp[x1][y1][x2][y2][k]=min(dp[x1][y1][x2][y2][k],find(x1,y1,x2,j,k-1)+find(x1,j+1,x2,y2,0));
}
return dp[x1][y1][x2][y2][k];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++) scanf("%d",&mp[i][j]);
}
for(int i=1;i<=8;i++){
for(int j=1;j<=8;j++) sum[i][j]=sum[i][j-1]+mp[i][j];
}
for(int j=1;j<=8;j++){
for(int i=1;i<=8;i++) sum[i][j]+=sum[i-1][j];
}
ave=1.0*sum[8][8]/n;
printf("%.3lf",sqrt(1.0*find(1,1,8,8,n-1)/n));
}
/*
样例:
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
ans:1.633
*/
心得
一次AC万岁!!!!
这道dp做的还是不错的,尤其是很快发现了解析中提到的两条关键结论,从而带出了思路
所以要仔细审题!!
dp就像数学平几,有时候思路一下子通了就不难了awa