[经典dp] HDU - 2517 棋盘分割 [废话流详解]

今天真是被虐到死.......

不说废话了...分析下题目:

题意: 一个8x8的棋盘, 每个小格子有一个值v(0<v<100), 现在要把格子切成n块, 切n-1刀, 每刀把棋盘切去一块不要, 然后剩下的那块继续这样切. 注意: 所谓不要的那块是以后都不能再去切了.(话说开始硬是没看懂题目那图是咋回事....), 这个理解很重要啊, 直接关系到解法.....然后切成的N块, 每块的值为它包含的小格子的Σv. 令e为N块的值的标准差, 问你e最小能为多少.

思路: 

1/我们先来考虑怎么切.

从简单入手..深搜吧...怎么写? 显然阶段就一个, 第几刀, 切完回溯. 每个阶段的决策呢? 根据题意, 每切一刀都要选择下那一块留下, 那一块丢掉(不再切), 由于是二维的, 可以横着切也可以竖着切..所以2x2四种决策.

2/怎么描述状态

我们先看下题目让我们求什么, 标准差 e = sqrt( Σ((xi-x_)^2)/n ) ,x_ 表示均值. 把右边展开得到, 

e^2 = Σ(x^2+x_^2-2*xi*x_)/n = (Σxi^2 + nx_^2 - 2x_Σxi )/n = ( Σxi^2 )/n - x_^2 . 可见要使e最小, 则使Σxi^2 最小, 即块平方和最小. 所以我们状态的值就应该是这个状态继续下去到N-1刀切完的时候所能达到的块最小平方和.

那怎么确定维度呢? 上面说了阶段是刀数, 那我们就选已经切了k刀的状态, 又由题意, 每次切后只需保留新产生的两块中的一块继续切, 所以我们就用四维状态[x1][y1][x2][y2]来表示矩形的左上角,右下角来描述一个矩形.(为什么不是保留场宽就可以?因为不同格子的值不一样...) 综上所述, 我们选五维状态 d[k][x1][y1][x2][y2] , 来表示 切完k刀后选矩形(x1,y1)(x2,y2)继续切, 切到N刀的时候所能达到的最小 块平方和.

3/怎么维护矩形和

利用迭代(尽量利用已知)的思想,

s[x][y], 表示0,0 到 x,y 的矩形和.

sum[x1][y1][x2][y2] = s[x2][y2]-s[x1-1][y1]-s[x1][y1-1]+s[x1-1][y1-1].

FOR(i, 0, n-1)		//初始化和
{
	FOR(j, 0, n-1)
	{
		if(!i && !j) s[0][0] = G[0][0];
		else if(!i) s[i][j] = G[0][j]+s[0][j-1];
		else if(!j) s[i][j] = G[i][0]+s[i-1][0];
		else
		{
			s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+G[i][j];
		}
	}
}
4/ TLE肿么办.....

按上面那样dfs的框架有了, 但是重复状态很多, 会TLE.... 所以, 记忆化搜索咯...记录每个状态的值不重复计算咯.....所以dp咯.....

wait, 能不能dfs+剪枝过? 

先考虑为什么会有重复. 比如, 竖着切两刀, 那先切左边那刀跟先切右边那刀效果是一样的. 恩,没错这是重复了, 但是这并没有说服力, 因为你可以随便加个最优化剪枝就行了, (维护个pre量使切的序列保持有序就行咯?比如从左到右). 但是考虑这种情况, 竖着切横着切成一个倒向左边的 T字, 然后取右上矩形继续切, 跟横着切竖着切成一个正立T字,同样取右上部分(假定这两个右上部分一样). 这尼玛是赤裸裸的子状态重复啊.....而且似乎没有什么剪枝的方法....因为你没法使切的序列保持有序....

5/ OK, 那给出状态转移方程

d[k][x1][y1][x2][y2] = min{ 

d[k+1][x1][y1][x][y2]+sum[x+1][y1][x2][y2], //竖着切留左边 

d[k+1][x+1][y1][x2][y2]+sum[x1][y1][x][y2], //竖着切留右边

, x∈[x1, x2-1].

d[k+1][x1][y1][x2][y]+sum[x1][y+1][x2][y2], //横着切留上边

d[k+1][x1][y+1][x2][y2]+sum[x1][y1][x2][y]. //横着切留下边

, y∈[y1, y2-1].

}

OK, 都弄懂了, 开始AC吧~~~


代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<string>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
inline int Rint() { int x; scanf("%d", &x); return x; }
inline int max(int x, int y) { return (x>y)? x: y; }
inline int min(int x, int y) { return (x<y)? x: y; }
#define FOR(i, a, b) for(int i=(a); i<=(b); i++)
#define FORD(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(x) for(int i=0; i<(x); i++)
typedef long long int64;
#define INF (1<<30)
#define bug(s) cout<<#s<<"="<<s<<" "

//dp, 记忆化搜索(凡是用深搜做会用很多重叠子问题的貌似都可以用这个).

#define MAXN 10
#define MAXK 20	//块数 n(1 < n < 15)。 
int G[MAXN][MAXN];
int k;
int n = 8;

int s[MAXN][MAXN];		//0,0 到x, y 的和
int sum(int x1, int y1, int x2, int y2)	//块和的平方
{
	int ret = s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
	return ret*ret;
}

int d[MAXK][MAXN][MAXN][MAXN][MAXN];	//表示块的平方和
int dp(int cur, int x1, int y1, int x2, int y2)		//状态: 切 cur次后, 剩下矩形(x1,y1)(x2,y2), 最后切完能获得的最小平方和.
{
	if(d[cur][x1][y1][x2][y2]!=-1) return d[cur][x1][y1][x2][y2];	//记忆化
	//else if(cur==k+1) return 0;	//边界情况不能这样, 因为切切切到最后一块, 那一块是全都要的.....!!!.wa1
	else if(cur == k)	//切k次后
	{
		return sum(x1, y1, x2, y2);	//把整块的值平方返回
	}
	else
	{
		int minx = INF;
		//竖切
		FOR(x, x1, x2-1)	//[x1, x], [x+1, x2]
		{
			//取左边
			int ret = dp(cur+1, x1, y1, x, y2);			//ret表示剩下的部分, 将继续分割
			minx = min(minx, ret+sum(x+1, y1, x2, y2));		//	+sum(x+1, y1, x2, y2) 表示切下的部分, 不再分割
			//取右边
			ret = dp(cur+1, x+1, y1, x2, y2);
			minx = min(minx, ret+sum(x1, y1, x, y2));
		}
		//横切
		FOR(y, y1, y2-1)	//[y1, y] [y+1, y2]
		{
			//取上边
			int ret = dp(cur+1, x1, y1, x2, y);
			minx = min(minx, ret+sum(x1, y+1, x2, y2));
			//取下边
			ret = dp(cur+1, x1, y+1, x2, y2);
			minx = min(minx, ret+sum(x1, y1, x2, y));
		}
		//bug(cur);bug(x1);bug(y1);bug(x2);bug(y2);bug(minx)<<endl;
		return d[cur][x1][y1][x2][y2] = minx;
	}
}

int main()
{
	k = Rint();
	k--;	// 切 k 次
	FOR(i, 0, n-1)
	{
		FOR(j, 0, n-1)
		{
			G[i][j] = Rint();
		}
	}
	//s[0][0] = G[0][0];
	FOR(i, 0, n-1)		//初始化和
	{
		FOR(j, 0, n-1)
		{
			if(!i && !j) s[0][0] = G[0][0];
			else if(!i) s[i][j] = G[0][j]+s[0][j-1];
			else if(!j) s[i][j] = G[i][0]+s[i-1][0];
			else
			{
				s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+G[i][j];
				//printf("s[%d][%d] = %d+%d-%d+%d\n", i, j, s[i-1][j], s[i][j-1], s[i-1][j-1], G[i][j]);
			}
		}
	}
	memset(d, -1, sizeof(d));
	int ret = dp(0, 0, 0, 7, 7);	//最小分块平方和
	double ret2 = (double)s[7][7]/(k+1);		//卧槽...平均值竟然用了int........WA3
	ret2*=ret2;	 //平均平方和
	//bug(k+1);bug(s[7][7]);bug(ret2)<<endl;
	double e = sqrt((double)ret/(k+1)-ret2);
	printf("%.3lf\n", e);
}



  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

泳裤王子

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值