codeforces 373E Watching Fireworks is Fun 单调队列优化dp+滚动数组

http://codeforces.com/contest/373/problem/E
A festival will be held in a town’s main street. There are n sections in the main street. The sections are numbered 1 through n from left to right. The distance between each adjacent sections is 1.

In the festival m fireworks will be launched. The i-th (1 ≤ i ≤ m) launching is on time ti at section ai. If you are at section x (1 ≤ x ≤ n) at the time of i-th launching, you'll gain happiness value bi - |ai - x| (note that the happiness value might be a negative value).

You can move up to d length units in a unit time interval, but it's prohibited to go out of the main street. Also you can be in an arbitrary section at initial time moment (time equals to 1), and want to maximize the sum of happiness that can be gained from watching fireworks. Find the maximum total happiness.

Note that two or more fireworks can be launched at the same time.

Input

The first line contains three integers n, m, d (1 ≤ n ≤ 150000; 1 ≤ m ≤ 300; 1 ≤ d ≤ n).

Each of the next m lines contains integers ai, bi, ti (1 ≤ ai ≤ n; 1 ≤ bi ≤ 109; 1 ≤ ti ≤ 109). The i-th line contains description of the i-th launching.

It is guaranteed that the condition ti ≤ ti + 1 (1 ≤ i < m) will be satisfied.

Output

Print a single integer — the maximum sum of happiness that you can gain from watching all the fireworks.

Please, do not write the %lld specifier to read or write 64-bit integers in C++. It is preferred to use the cin, cout streams or the %I64d specifier.

Examples
Input

50 3 1
49 1 1
26 1 4
6 1 10

Output

-31

Input

10 2 1
1 1000 4
9 1000 4

Output

1992

题目大意:城镇中有 n n n个位置, m m m个烟花要放,你每秒可以移动 d d d单位距离。第i个烟花放的时间记为 t i t_{i} ti,放出的位置记为 a i a_{i} ai,假设放出的时候你位于位置 x x x,那么你将会获得 b i − ∣ a i − x ∣ b_{i}-|a_{i}-x| biaix的快乐值。初始的时候你可以在任意一个位置上,问能获得的最大的快乐值是多少。
思路:设 f [ i ] [ j ] f[i][j] f[i][j]表示在放第 i i i个烟花时,你位于 j j j位置上所能获得的最大快乐,那么可以写出转移方程: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] + b [ i ] − ∣ a [ i ] − j ∣ ) f[i][j]=max(f[i-1][k]+b[i]-|a[i]-j|) f[i][j]=max(f[i1][k]+b[i]a[i]j) 其 中 k 满 足 : j − ( t i − t i − 1 ) ∗ d &lt; = k &lt; = j + ( t i − t i − 1 ) ∗ d 其中k满足:j-(t_{i}-t_{i-1})*d&lt;=k&lt;=j+(t_{i}-t_{i-1})*d kj(titi1)d<=k<=j+(titi1)d若暴力枚举的话复杂度为: O ( n 2 m ) O(n^{2}m) O(n2m),会超时的,我们需要优化时间复杂度。
我们发现 b [ i ] − ∣ a [ i ] − j ∣ 只 与 b[i]-|a[i]-j|只与 b[i]a[i]j i 、 j i、j ij有关,因此可以把这一项提出来,将原方程写成: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ k ] ) + b [ i ] − ∣ a [ i ] − j ∣ f[i][j]=max(f[i-1][k])+b[i]-|a[i]-j| f[i][j]=max(f[i1][k])+b[i]a[i]j m a x ( f [ i − 1 ] [ k ] ) max(f[i-1][k]) max(f[i1][k])只和上一状态中连续的一段最大值有关,因此可以用单调队列来优化。将 f i − 1 f_{i-1} fi1构造成一个单调队列,可以在均摊 O ( 1 ) O(1) O(1)的时间复杂度内求出 m a x ( f [ i − 1 ] [ k ] ) max(f[i-1][k]) max(f[i1][k])的值。这样时间复杂度就降到了 O ( n m ) O(nm) O(nm)。这题的另外一个坑点就是空间不够你开300*150000的空间的,然而我们计算 f i f_{i} fi又只用到了 f i − 1 f_{i-1} fi1,因此用滚动数组就好了。

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;

ll a[305],b[305],t[305];
ll dp[2][150005];
int n,m;
ll d;
ll q[150005];
int fon=1,bak=1;

int main()
{
    scanf("%d%d%lld",&n,&m,&d);
    for(int i=1;i<=m;i++)
        scanf("%lld%lld%lld",&a[i],&b[i],&t[i]);
    for(int i=1;i<=n;i++)
        dp[0][i]=b[1]-abs(a[1]-i);
    ll time=t[1];
    int times=0;
    for(int i=2;i<=m;i++)
    {
        times^=1;
        fon=bak=1;
        if(time==t[i])//ͬʱ
            for(int j=1;j<=n;j++)
                dp[times][j]=dp[times^1][j]+b[i]-abs(a[i]-j);
        else
        {
            ll dis=t[i]-time,pos=1;
            time=t[i];
            for(int j=1;j<=n;j++)
            {
                while(pos<=n&&pos<=j+dis*d)
                {
                    while(fon<bak&&dp[times^1][pos]>dp[times^1][q[bak-1]])
                        --bak;
                    q[bak++]=pos++;
                }
                while(fon<bak&&j-dis*d>q[fon])
                    ++fon;
                dp[times][j]=dp[times^1][q[fon]]+b[i]-abs(a[i]-j);
            }
        }
    }
    ll ans=-1e16;
    for(int i=1;i<=n;i++)
        ans=max(ans,dp[times][i]);
    printf("%lld\n",ans);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值