题目链接:http://poj.org/problem?id=1191 NOI 1999
在看黑书(算法艺术与信息学竞赛)时,看到了这道题,之后在POJ上成功AC,以下写出我的题解。
版权所有,侵权必究!!!
题目分析:因为方差公式比较复杂,可先将其变形,变形计算如下图:(本人手写,不喜勿喷。)
由于平均值是一定的,所以只需要让每个矩形的总分的平方和最小。设左上角为(x1,y1),右下角为(x2,y2)的矩形中总和为s[x1][y1][x2][y2],次矩形切割k次得到k+1块矩形的总平方和的最小值为d[k][x1][y1][x2][y2],则它可以沿着横着切也可以沿着竖着切,然后选一块继续切,状态转移方程为:
d[k][x1][y1][x2][y2]=min{
min(min(d[k-1][x1][y1][cut][y2]+s[cut+1][y1][x2][y2],d[k-1][cut+1][y1][x2][y2]+s[x1][y1][cut][y2]),d[k][x1][y1][x2][y2]),(x1<=cut<x2)
min(min(d[k-1][x1][y1][x2][cut]+s[x1][cut+1][x2][y2],d[k-1][x1][cut+1][x2][y2]+s[x1][y1][x2][cut]),d[k][x1][y1][x2][y2]);(y1<=cut<y2)
}
设m为棋盘边长,则状态数目为(m^4)*n,决策数目为O(m),预处理先用O(m*m),时间算出左上角为(1,1)的所有矩阵元素和,这样状态转移的时间就是O(1),故总的时间复杂度为O((m^5)*n)。由于m=8,n<=15,所以这个方法很高效。
代码如下:
<span style="font-size:14px;">/*2014.8.25 POJ1191《棋盘分割》 程序猿:卢裕东*/
#include<iostream>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const double maxn=999999999;
int N,map[10][10];
double average,total=0,ans;
double s[10][10][10][10],dp[20][10][10][10][10];//dp[k][x1][y1][x2][y2]表示切割k次之后左上角为(x1,y1),右下角为(x2,y2)的矩形的总分平方和的最小值。
inline double min(double a,double b)
{
return a<b?a:b;
}
void init()
{
scanf("%d",&N);
for(int i=0;i<8;i++)
for(int j=0;j<8;j++)
{
scanf("%d",&map[i][j]);
total+=map[i][j];
}
average=total/(N*1.0);
}
void Preprocessing()
{
for(int x1=0;x1<8;x1++)
for(int y1=0;y1<8;y1++)
for(int x2=x1;x2<8;x2++)
{
total=0;
for(int y2=y1;y2<8;y2++)
{
total+=map[x2][y2];
if(x2==x1) s[x1][y1][x2][y2]=total;
else s[x1][y1][x2][y2]=total+s[x1][y1][x2-1][y2];
}
}
}
void DP_initialize()
{
for(int x1=0;x1<8;x1++)
for(int y1=0;y1<8;y1++)
for(int x2=x1;x2<8;x2++)
for(int y2=y1;y2<8;y2++)//此处四重循环就是将前面的和值转化为平方然后赋值给DP数组
{
s[x1][y1][x2][y2]*=s[x1][y1][x2][y2];
dp[0][x1][y1][x2][y2]=s[x1][y1][x2][y2];
}
}
void DP()
{
for(int k=1;k<N;k++)//切割成N个矩形需要k-1次切割。
for(int x1=0;x1<8;x1++)
for(int y1=0;y1<8;y1++)
for(int x2=x1;x2<8;x2++)
for(int y2=y1;y2<8;y2++)
{
dp[k][x1][y1][x2][y2]=maxn;
double temp;
for(int cut=x1;cut<x2;cut++)//竖着枚举分割线
{
temp=min(dp[k-1][x1][y1][cut][y2]+s[cut+1][y1][x2][y2],dp[k-1][cut+1][y1][x2][y2]+s[x1][y1][cut][y2]);
dp[k][x1][y1][x2][y2]=min(temp,dp[k][x1][y1][x2][y2]);
}
for(int cut=y1;cut<y2;cut++)//横着枚举分割线
{
temp=min(dp[k-1][x1][y1][x2][cut]+s[x1][cut+1][x2][y2],dp[k-1][x1][cut+1][x2][y2]+s[x1][y1][x2][cut]);
dp[k][x1][y1][x2][y2]=min(temp,dp[k][x1][y1][x2][y2]);
}//经过横着和竖着枚举分割线得出最优进行记录
}
ans=(dp[N-1][0][0][7][7])/(N*1.0)-average*average;
printf("%.3lf\n",sqrt(ans));
}
int main()
{
init();
Preprocessing();
DP_initialize();
DP();
return 0;
}</span>
如此可AC!