[区间DP] UVa1336 修缮长城 (未来费用的计算)

题目

这里写图片描述

思路

首先将修缮点按坐标从小到大排序,则已修复的点一定是一个连续的区间。这样就可以用d(i,j,k)表示已经修复完(i,j),机器人现在在k(k=0左边缘,k=1右边缘)时已经发生的总费用。
那么就有一个问题,费用根据时间计算,而状态里没有时间,那下一个修缮点的费用该如何计算?
此处类比颜色长度。由于所有修缮点肯定都要被修,那么我们先把所有的立刻修缮费用加起来作为初状态,每次机器人移动耗费了时间,就把耗费的时间乘以(i,j)外面的点的单位增加修缮费用,即可。
决策只有两种,往修一个点,或往右修一个点。


本题迷之WA,调了一晚上对拍了很久,只发现有小数精度的问题——std与my的ans只差1。我没法解决,所有本题仅需理解未来费用就行了,实际代码实现不用太过在意。

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
#define ld long double
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const ld eps = 1e-5;
const ll INF = 1000000000000000;  // INF定义成10000000,WA
const int maxn = 1000 + 10;

struct damage {
    int x, c, d;
    bool operator < (const struct damage &rhs) const {
        return x < rhs.x;
    }
}node[maxn];

int n, v, startx, sumc, vis[maxn][maxn][2];
ll sumd[maxn];
ld d[maxn][maxn][2];

// (i,j)已经修复,k=1表示机器在i,k=2表示机器在j
ld dp(int i, int j, int k) {
    if (i > j || i < 1 || j > n) return INF;
    ld &ans = d[i][j][k];
    if (vis[i][j][k]) return ans;

    ld ans1, ans2;
    if (k == 1) {
        ans1 = dp(i, j - 1, 0) + (sumd[i - 1] + sumd[n] - sumd[j - 1])*(abs((node[j].x - node[i].x)) / (ld)v);
        ans2 = dp(i, j - 1, 1) + (sumd[i - 1] + sumd[n] - sumd[j - 1])*(abs((node[j].x - node[j-1].x)) / (ld)v);
    }
    else {
        ans1 = dp(i + 1, j, 1) + (sumd[i] + sumd[n] - sumd[j])*abs((node[j].x - node[i].x)) / (ld)v;
        ans2 = dp(i + 1, j, 0) + (sumd[i] + sumd[n] - sumd[j])*abs((node[i+1].x - node[i].x)) / (ld)v;
    }

    ans = min(ans1, ans2);
    if (ans > INF) ans = INF;
    vis[i][j][k] = 1;
    return ans;
}

int main() {
    while (scanf("%d%d%d", &n, &v, &startx) == 3 && n && v && startx) {
        _rep(i, 1, n)
            scanf("%d%d%d", &node[i].x, &node[i].c, &node[i].d);

        sort(node + 1, node + 1 + n);

        // 计算dd的前缀和,sumd
        _rep(i, 1, n) sumd[i] = sumd[i - 1] + node[i].d;

        // 计算所有立即修复的费用,sumc
        sumc = 0;
        _rep(i, 1, n) sumc += node[i].c;

        memset(vis, 0, sizeof(vis));

        // 计算初状态
        int pos1, pos2;
        damage robot; robot.x = startx;
        damage* np = lower_bound(node + 1, node + 1 + n, robot);
        pos1 = np - node;
        pos2 = pos1 - 1;
        if (pos1 <= n + 1) { 
            d[pos1][pos1][0] = d[pos1][pos1][1] = sumc + (ld)sumd[n] * abs(startx - node[pos1].x) / (ld)v; 
            vis[pos1][pos1][0] = vis[pos1][pos1][1] = 1;
        }
        if (pos2 >= 0) {
            d[pos2][pos2][0] = d[pos2][pos2][1] = sumc + (ld)sumd[n] * abs(startx - node[pos2].x) / (ld)v;
            vis[pos2][pos2][0] = vis[pos2][pos2][1] = 1;
        }

        printf("%.0lf\n", floor(min(dp(1, n, 0), dp(1, n, 1))));  // 注意,floor(double)的返回值还是double
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值