今天真是被虐到死.......
不说废话了...分析下题目:
题意: 一个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); }