Codeforces 721E Road to Home

题意

输入第一行有4个数,分别为\(L,n,p,t\),分别表示总长度为\(L\)的路,中间有\(n\)个互不相交的区间,现在要用长度为\(p\)的小木棒从左往右铺路(木棒不能被折断,也不能有重叠,且所有的木棒必须在区间内),你可以连续铺路,但是一旦你主动或被迫中断铺路(比如区间内剩下的长度不足以放下一根木棍)那么下一根开铺的木棍最少也要在\(t\)距离之后。问最多能铺多少根木棍。接下来的\(n\)行表示从左到右的\(n\)个区间。

想法

首先当然是想暴力的算法...dp?
\(f_i\)表示铺完前\(i\)个区间最多能铺的木棍数,\(g_i\)表示前\(i\)个区间铺了最多的木棍时最右端的木棍的右端最左可以取到的地方。
暴力转移?
\[f_i = \max_{ g_j+t<=r_i} \{ f_j + \lfloor \frac{r_i - \max \{l_i, g_j + t\}}{p} \rfloor \}\]
\[g_i = \max \{ l_i, g_j + t\} + \lfloor \frac{r_i - \max \{l_i, g_j + t\}}{p} \rfloor * p\]
之后由观察/打表可以发现,\(g_i\)是不减的,决策也是不减的,那么这就可以用到“单调队列”。
每次转移时不断从队头取出元素,如果有更优的决策那么就从队头pop出去,更新了就把得到的状态加入到队尾。
注意这里的更优不仅要考虑\(f_i\)的更大,在\(f_i\)相同时也要考虑\(g_i\)的更小。

Code
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define ll long long
#define db double
#define N 100010
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define swap(T, a, b) ({T ttt = a; a = b; b = ttt;})
#define x f[q[tl]]
#define y g[q[tl]]
int L, n, p, t, f[N], g[N], l[N], r[N], Ans = 0, q[N], tl, tr, tA, tB;
bool Q; 
int main()
{
    scanf("%d%d%d%d", &L, &n, &p, &t);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &l[i], &r[i]);
    f[0] = 0; g[0] = -t; q[tl = tr = 0] = 0; 
    for (int i = 1; i <= n; i++)
    {
        tA = tB = 0; Q = false;  
        while (tl <= tr && y + t <= r[i] && x + (r[i] - max(l[i], y + t)) / p > tA)
        {
            tA = x + (r[i] - max(l[i], y + t)) / p;
            tl++; Q = true; 
        }
        if (Q) tl--;
        else tA = x + (r[i] - max(l[i], y + t)) / p;
        tB = max(l[i], y + t) + (r[i] - max(l[i], y + t)) / p * p;
        Q = false; 
        while (tl <= tr && y + t <= r[i] && x + (r[i] - max(l[i], y + t)) / p == tA)
        { 
            tB = min(tB, max(l[i], y + t) + (r[i] - max(l[i], y + t)) / p * p);
            tl++; Q = true; 
        }
        if (Q) tl--;  
        f[i] = tA; g[i] = tB;
        if (f[i] > Ans) { Ans = f[i]; q[++tr] = i; } 
    }
    printf("%d\n", Ans); 
    return 0;
}

转载于:https://www.cnblogs.com/zkGaia/p/6097412.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值