您的位置:首页 > 其它

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

2012-08-03 20:33 295 查看
今天真是被虐到死.......

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

题意: 一个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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: