【动态规划】【决策单调性优化】【NOI2004】小H的小屋

【问题描述】
小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;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值