ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

[NOI1999] 棋盘分割

2021-08-15 15:32:36  阅读:172  来源: 互联网

标签:分割 bar NOI1999 sum times frac 棋盘 sigma


【NOI1999】 棋盘分割

题目描述

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

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成 \(n\) 块矩形棋盘,并使各矩形棋盘总分的均方差最小。

均方差 \(\sigma=\sqrt{\dfrac{\sum^{n}_{i=1}\times(x_i-\bar x)^2}{n}}\),其中平均值 \(\bar x=\dfrac{\sum_{i=1}^{n}x_i}{n}\),\(x_i\)​ 为第 \(i\) 块矩形棋盘的分。

请编程对给出的棋盘及 \(n\),求出 \(\sigma\) 的最小值。

输入格式

第一行为一个整数 \(n(1<n<15)\)。

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

输出格式

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

题目分析

终于从 y 总那里听懂了,于是赶快来写篇题解。

一道区间 DP。

设平均值 \(\bar x = \dfrac{\sum_{i=1}^{n}x_i}{n}\),

方差 \(\sigma = \sqrt{\dfrac{\sum_{i=1}^{n}\times (x_i-\bar x)^2}{n}}\)

由于我们要使方差 \(\sigma\) 最小,那么不妨将方差平方后再进行化简(过程我会写的有些繁琐,可以跳着看):

\(\sigma^2=\dfrac{\sum_{i=1}^{n}\times (x_i-\bar x)^2}{n}\)

\(\sigma^2=\dfrac{1}{n}\times (\sum_{i=1}^{n}\times({x_i}^2 +{\bar x}^2 - 2\times x_i\times \bar x))\)

\(\sigma^2 = \frac{1}{n}(\sum_{i=1}^n{x_i}^2 - 2\sum_{i=1}^nx_i\times\bar{x} + \sum_{i=1}^n\bar{x}^2)\)

\(\sigma^2 = \frac{1}{n}\sum_{i=1}^nx_i^2 - 2\bar{x}\times\frac{1}{n}\sum_{i=1}^nx_i + \frac{1}{n}\times n\bar{x}^2\)

发现第二项的 \(\frac{1}{n}\sum_{i=1}^nx_i\) 正好就是 \(\bar x\),于是:

\(\sigma^2 = \frac{1}{n}\sum_{i=1}^nx_i^2 - 2\overline{x}^2 + \bar{x}^2\)

\(\sigma^2 = \frac{1}{n}\sum_{i=1}^nx_i^2 - \bar{x}^2\)

\(\bar x\) 很明显是一个可以马上求出的数,并且我们需要频繁地求矩阵和,所以考虑使用二维前缀和 \(O(1)\) 查询矩阵和,大大加快了效率。

在这篇题解中,我们采用 \((x1,y1,x2,y2)\) 的方式表示一个矩阵,其左上角为 \((x1,y1)\),右下角为 \((x2,y2)\)。

令 \(dp[a][b][c][d][k]\) 表示划分到 \(k-1\) 个的子矩阵,是以 \((a,b)\) 为左上角,\((c,d)\) 为右下角。

我们可以采用记忆化方法来完成此过程。

//2021/8/9

#include <iostream>

#include <cstdio>

#include <cmath>

#include <cstring>

#include <algorithm>

#define debug(c) cerr<<#c<<"="<<c<<endl

using namespace std;

const int ma=10,maxn=16;

const double INF=1e9;

int n;

int sum[ma][ma];

double dp[ma][ma][ma][ma][maxn];

double bar;//平均值 

//如果是C++11可以这样,但是有些编译器因为这里又有cmath又有y1 y2,会CE 
//当然就算报错,换个名就好了
 
inline double getsum(int x1,int y1,int x2,int y2)
{
	double res=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]-bar;
	
	return res*res;
}

inline double dfs(int x1,int y1,int x2,int y2,int k)//记忆化方法 
{
	double &tmp=dp[x1][y1][x2][y2][k];
	
	if(tmp>=0)
	{
		return tmp;
	}
	
	if(k==1)
	{
		return getsum(x1,y1,x2,y2);
	}
	
	tmp=INF;
	
	//横着去切 
	
	for(register int i=x1;i<x2;i++)
	{
		tmp=min(tmp,dfs(x1,y1,i,y2,k-1)+getsum(i+1,y1,x2,y2));
		
		tmp=min(tmp,dfs(i+1,y1,x2,y2,k-1)+getsum(x1,y1,i,y2)); 
	}
	
	//竖着去切 
	
	for(register int i=y1;i<y2;i++)
	{
		tmp=min(tmp,dfs(x1,y1,x2,i,k-1)+getsum(x1,i+1,x2,y2));
		
		tmp=min(tmp,dfs(x1,i+1,x2,y2,k-1)+getsum(x1,y1,x2,i));
	}
	
	return tmp;
}

int main(void)
{
	scanf("%d",&n);
	
	for(register int i=1;i<=8;i++)
	{
		for(register int j=1;j<=8;j++)
		{
			scanf("%d",&sum[i][j]);
			
			sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
		}
	}
	
	memset(dp,-1,sizeof(dp));
	
	bar=(double)sum[8][8]/n;
	
	printf("%.3lf\n",sqrt(dfs(1,1,8,8,n)/n));
	
	return 0;
}

标签:分割,bar,NOI1999,sum,times,frac,棋盘,sigma
来源: https://www.cnblogs.com/Coros-Trusds/p/15143522.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有