【问题描述】
小H发誓要做21世纪最伟大的数学家。他认为,做数学家与做歌星一样,第一步要作好包装,不然本事再大也推不出去。为此他决定先在自己的住所上下功夫,让人一看就知道里面住着一个“未来的大数学家”。
为了描述方便,我们以向东为x轴正方向,向北为y轴正方向,建立平面直角坐标系。小H的小屋东西长为100Hil(Hil是小H自己使用的长度单位,至于怎样折合成“m”,谁也不知道)。东墙和西墙均平行于y轴,北墙和南墙分别是斜率为k1和k2的直线,k1和k2为正实数。北墙和南墙的墙角处有很多块草坪,每块草坪都是一个矩形,矩形的每条边都平行于坐标轴。相邻两块草坪的接触点恰好在墙上,接触点的横坐标被称为它所在墙的“分点”,这些分点必须是1到99的整数。
小H认为,对称与不对称性的结合才能充分体现“数学美”。因此,在北墙角要有m块草坪,在南墙角要有n块草坪,并约定m≤n。如果记北墙和南墙的分点集合分别为X1,X2,则应满足X1含于X2,即北墙的任何一个分点一定是南墙的分点。
由于小H目前还没有丰厚的收入,他必须把草坪的造价降到最低,即草坪的占地总面积最小。你能编程帮他解决这个难题吗?
【输入文件】
仅一行,包含4个数k1,k2,m,n。k1和k2为正实数,分别表示北墙和南墙的斜率,精确到小数点后第一位。m和n为正整数,分别表示北墙角和南墙角的草坪的块数。
【输出文件】
一个实数,表示草坪的最小占地总面积。精确到小数点后第一位。
【约定】
2≤m≤n≤100
南北墙距离很远,不会出现南墙草坪和北墙草坪重叠的情况
【样例输入】
0.5 0.2 2 4
【样例输出】
3000.0
【评分标准】
对于每个测试点,如果你的结果与标准答案的误差不超过0.1,则可以得到该测试点的满分,否则得0分。
朴素的动态规划时间复杂度为O(L^2 · m^2 · n),但是此题恰好可以卡过。
首先预处理g[k][i]即把东西长度为k的区域分给南墙,分成i块的最小面积。
可以轻易得出g[k][i] = g[k`][i - 1] + (k - k`)^2 * K2 (k` < k)。
然后在计算f[k][i][j]即把东西长度为k的区域分给南北两边的墙,北墙分成i块,南墙分成j块的最小面积。
则有:f[k][i][j] = f[k`][i - 1][j`] + g[k - k`][j - j`] (k` < k, j` < j)
最后的答案就是f[100][m][n]。
代码:
/****************************\
* @prob: NOI2004 hut *
* @auth: Wang Junji *
* @stat: Accepted. *
* @date: June. 10th, 2012 *
* @memo: 动态规划、预处理 *
\****************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
template <typename _Tp> inline void gmin(_Tp& a, _Tp b) {if (b < a) a = b; return;}
template <typename _Tp> inline const _Tp sqr(_Tp x) {return x * x;}
const int maxN = 110;
const double INF = 1e198;
double f[maxN][maxN][maxN], g[maxN][maxN], K1, K2;
int m, n;
int main()
{
freopen("hut.in", "r", stdin);
freopen("hut.out", "w", stdout);
scanf("%lf%lf%d%d", &K1, &K2, &m, &n);
for (int i = 0; i < maxN; ++i)
for (int j = 0; j < maxN; ++j)
g[i][j] = INF;
g[0][0] = 0;
for (int k = 1; k < 101; ++k)
for (int i = 1; i < k + 1; ++i)
{
g[k][i] = INF;
for (int x = i - 1; x < k; ++x)
gmin(g[k][i], g[x][i - 1] + sqr(k - x) * K2);
}
for (int i = 0; i < maxN; ++i)
for (int j = 0; j < maxN; ++j)
for (int k = 0; k < maxN; ++k)
f[i][j][k] = INF;
f[0][0][0] = 0;
for (int k = 1; k < 101; ++k)
for (int i = 1; i < m + 1; ++i)
for (int j = i; j < n + 1; ++j)
{
f[k][i][j] = INF;
for (int x = i - 1; x < k; ++x)
for (int j1 = i - 1; j1 < j; ++j1)
gmin(f[k][i][j], f[x][i - 1][j1]
+ sqr(k - x) * K1 + g[k - x][j - j1]);
}
printf("%.1lf\n", f[100][m][n]);
return 0;
}
可以发现之前的方程具有决策单调性。
也就是说,f[k][i][j]的决策k`、j`一定不小于f[k][i - 1][j]的决策k``、j``,
那么每次枚举k`、j`的时候就可以从k``、j``开始枚举。
这样可以把时间复杂度降到O(L^2 · m · n)。
代码:
/*************************************************\
* @prob: NOI2004 hut * @auth: Wang Junji *
* @stat: Accepted. * @date: June. 11th, 2012 *
* @memo: 动态规划、预处理、决策单调性优化 *
\*************************************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
template <typename _Tp> inline void gmin(_Tp& a, _Tp b) {if (b < a) a = b; return;}
template <typename _Tp> inline const _Tp sqr(_Tp x) {return x * x;}
const int maxN = 110;
const double INF = 1e198;
double f[maxN][maxN][maxN], g[maxN][maxN], K1, K2;
int dec[maxN][maxN][maxN][2], m, n;
int main()
{
freopen("hut.in", "r", stdin);
freopen("hut.out", "w", stdout);
scanf("%lf%lf%d%d", &K1, &K2, &m, &n);
for (int i = 0; i < maxN; ++i)
for (int j = 0; j < maxN; ++j)
g[i][j] = INF;
g[0][0] = 0;
for (int k = 1; k < 101; ++k)
for (int i = 1; i < k + 1; ++i)
{
g[k][i] = INF;
for (int x = i - 1; x < k; ++x)
gmin(g[k][i], g[x][i - 1] + sqr(k - x) * K2);
}
for (int i = 0; i < maxN; ++i)
for (int j = 0; j < maxN; ++j)
for (int k = 0; k < maxN; ++k)
f[i][j][k] = INF;
f[0][0][0] = 0;
for (int k = 1; k < 101; ++k)
for (int i = 1; i < m + 1; ++i)
for (int j = i; j < n + 1; ++j)
{
f[k][i][j] = INF;
for (int x = dec[k][i - 1][j][0]; x < k; ++x)
for (int j1 = dec[k][i - 1][j][1]; j1 < j; ++j1)
if (f[x][i - 1][j1] + sqr(k - x) * K1 + g[k - x][j - j1] < f[k][i][j])
f[k][i][j] = f[x][i - 1][j1] + sqr(k - x) * K1 + g[k - x][j - j1],
dec[k][i][j][0] = x, dec[k][i][j][1] = j1;
}
printf("%.1lf\n", f[100][m][n]);
return 0;
}