POJ1821-Fence

这是一道经典的单调队列优化DP.
首先不难看出要用动态规划求解,dp[i][j]表示第i个工人粉刷第j块木板时的最大收益,由此
dp[i][j] = max(
dp[i-1][j] 第i个工人不刷木板
dp[i-1][k] + (j - k) * p 第i个工人从第k+1块木板开始一直刷到第j块
)
由于每个工人粉刷的木板必须包含s,因此dp[i][…]只跟dp[i-1][…]有关。
另外前两个方程已经是O(1)时间的,而第三个方程可以变为
max(dp[i-1][k] - k * p) + j * p,因此我们将括号内的数值用一个单调队列维护,然后枚举i,j,k即可。
维护单调队列时我们保证val(dp[i-1][k] - k * p)从大到下,pos从小到大,从队尾不断删除比要插入的小的值,查询时从队首不断抛出,第一个满足位置区间的即是最大的满足条件的val。
注意k是已经粉刷完毕的最后一块木板,k+1是该工人需要粉刷的第一块木板,而j是最后一块需要粉刷的木板:
s - l + 1 <= k + 1 <= s
s - l <= k < s
s <= j <= j + l - 1
搞定需要枚举的区间后剩下的就很简单了。

#include <cstdio>
#include <deque>
#include <algorithm>

using namespace std;

const int maxn = 160000 + 10;
const int maxm = 100 + 10;

struct worker {
    int l, p, s;
    bool operator < (const worker& w) const {
        return s < w.s;
    }
};

worker a[maxm];
int dp[maxm][maxn];

// dp[i][j] = dp[i-1][k] + (j-k) * p

int main(int argc, char const *argv[]) {
    int n, m;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d", &a[i].l, &a[i].p, &a[i].s);
    }
    sort(a + 1, a + m + 1);

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            dp[i][j] = dp[i-1][j];
        }
        //first == val, second == pos
        deque<pair<int, int> > deq;
        for (int k = max(a[i].s - a[i].l, 0); k < a[i].s; k++) {
            int val = dp[i-1][k] - k * a[i].p;
            while (!deq.empty() && deq.back().first <= val) {
                deq.pop_back();
            }
            deq.push_back(make_pair(val, k));
        }

        for (int j = a[i].s; j < a[i].s + a[i].l; j++) {
            while (!deq.empty() && deq.front().second < j - a[i].l) {
                deq.pop_front();
            }
            dp[i][j] = max(dp[i][j],deq.front().first + j * a[i].p);
        }
    }
    printf("%d\n", *max_element(dp[m] + 1, dp[m] + n + 1));

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值