CodeForces 372C--Watching Fireworks is Fun(单调队列优化DP)

题目链接:https://codeforces.com/problemset/problem/372/C

题目大意:有$n$个路段,$m$次放烟花,第$i$个烟花在第$t_i$时间在第$a_i$个路段燃放,可获得的价值为$b_i$,如果第$i$个烟花燃放的时候你在位置$x$那么,你将会获得$b_i-|a_i-x|$的价值,你每个单位时间可以移动$d$的长度,初始位置可以随意,问你最后能获得的最大价值是多少

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

直接暴力DP的话应该很容易想到,定义DP状态为$dp[i][j]$表示第$i$个烟花燃放的时候在第$j$位置能够得到的最大价值,那么转移方程也很好写:$dp[i][j]=max(dp[i][j],dp[i-1][k]+fire[i].b-abs(fire[i].a-j))(kepsilon [j-h,j+h])$,那么第一维由于只用到了上一个状态,那么可以直接复制一下上一个状态就好了,暴力DP代码如下:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long ll;
const int mac=2e5+10;
const ll inf=1e18+10;

struct node
{
    int a;
    ll b,t;
}fire[mac];
int q[mac];
ll dp[mac],f[mac];

int main(int argc, char const *argv[])
{
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n,m,d;
    cin>>n>>m>>d;
    for (int i=1; i<=m; i++){
        int a,b,t;
        cin>>a>>b>>t;
        fire[i]=node{a,b,t};
    }
    int head=1,tail=0;
    ll ans=-inf;
    fire[0]=node{0,0,fire[1].t};
    for (int i=1; i<=m; i++){//枚举烟花
        memcpy(f,dp,sizeof dp);
        for (int j=1; j<=n; j++){
            ll h=(fire[i].t-fire[i-1].t)*d;
            dp[j]+=fire[i].b-abs(fire[i].a-j);
            for (int k=max(1LL,j-h); k<=min((ll)n,j+h); k++){
                dp[j]=max(dp[j],f[k]+fire[i].b-abs(fire[i].a-j));
            }
            if (i==m) ans=max(ans,dp[j]);
        }
    }
    printf("%lld
",ans);
    return 0;
}
View Code

实际上我们只需要维护一下$f[k]$的在$j-h,j+h$的最大值就行了,那么我们可以想到单调队列,维护一个递减序列,我们先直接将整个递减序列构造出来,然后再对每个$j$只需要判断$j-h$是否大于q[head],这样就可以保证了区间的合法。

以下是AC代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

typedef long long ll;
const int mac=2e5+10;
const ll inf=1e18+10;

struct node
{
    int a;
    ll b,t;
}fire[mac];
int q[mac];
ll dp[mac],f[mac];

int main(int argc, char const *argv[])
{
    //freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    int n,m,d;
    cin>>n>>m>>d;
    for (int i=1; i<=m; i++){
        int a,b,t;
        cin>>a>>b>>t;
        fire[i]=node{a,b,t};
    }
    ll ans=-inf;
    for (int i=1; i<=m; i++){//枚举烟花
        memcpy(f,dp,sizeof dp);
        if (fire[i].t==fire[1].t){
            for (int j=1; j<=n; j++)
                dp[j]=f[j]+fire[i].b-abs(fire[i].a-j);
            continue;
        }
        int head=1,tail=0,k=1;
        ll h=(fire[i].t-fire[i-1].t)*d;
        for (int j=1; j<=n; j++){
            while (k<=min((ll)n,j+h)){
                while (head<=tail && f[k]>=f[q[tail]]) tail--;
                q[++tail]=k++;
            }
            while (head<=tail && j-h>q[head]) head++;
            dp[j]=f[q[head]]+fire[i].b-abs(fire[i].a-j);
        }
    }
    for (int i=1; i<=n; i++) ans=max(ans,dp[i]);
    cout<<ans<<endl;
    return 0;
}