C. Watching Fireworks is Fun(单调队列优化dp)

题目传送门

题意: 你在一个长度为n 的大街上,有m次烟花表演,分别在t[i]时刻上演,假设你现在站在位置x上,你观看第i次烟花表演,可以获得b[i]-abs(a[i]-x) 的幸福值(不能不看!),然后你每一个时刻可以移动d个单位长度,开始你可以在任意位置,不能走出大街,问你最多能获得多少幸福值?(可能是负数)。

数据范围:
1   ≤   n   ≤   150000 ; 1   ≤   m   ≤   300 ; 1   ≤   d   ≤   n 1 ≤ n ≤ 150000; 1 ≤ m ≤ 300; 1 ≤ d ≤ n 1n150000;1m300;1dn
1   ≤   a i   ≤   n ; 1   ≤   b i   ≤   1 0 9 ; 1   ≤   t i   ≤   1 0 9 1 ≤ a_i ≤ n; 1 ≤ b_i ≤ 10^9; 1 ≤ t_i ≤ 10^9 1ain;1bi109;1ti109
保证t[i]由小到大给出(好像没什么用的条件,sort一下就行)

题解: 看到这个数据范围,我们肯定能想到用 f [ i ] [ j ] f[i][j] f[i][j] 表示你正站在位置j在看第i场烟花所能获得的最大值,那么最后我们的答案就是 m a x ( f [ m ] [ j ] ) , j ∈ ( 1 , n ) max(f[m][j]),j∈(1,n) max(f[m][j])j(1,n)

然而,我们可以知道,第i次看烟花只会受第i-1次的影响,所以我们可以优化掉一维的空间,即得到式子 f [ j ] = m a x ( f [ k ] ) + b [ i ] − a b s ( a [ i ] − j ) , ( k ∈ ( m a x ( 1 , j − ( t [ i ] − t [ i − 1 ] ) ∗ d ) , m i n ( n , j + ( t [ i ] − t [ i − 1 ] ) ∗ d ) ) ) f[j]=max(f[k])+b[i]-abs(a[i]-j),(k∈(max(1,j-(t[i]-t[i-1])*d),min(n,j+(t[i]-t[i-1])*d))) f[j]=max(f[k])+b[i]abs(a[i]j),(k(max(1,j(t[i]t[i1])d),min(n,j+(t[i]t[i1])d)))

这不是维护一个RMQ吗,每次询问区间k中的最大值

如果我们用线段树去维护RMQ,这题的复杂度为 O ( n m l o g ( n ) ) O(nmlog(n)) O(nmlog(n)),遗憾超时

但是我们发现我们在遍历街道的时候,之前用过的并且以后不可能再用的信息可以舍去,这就可以用单调队列去维护我们的区间最值了(滑动窗口)。

代码:

#include<bits/stdc++.h>
#define endl '\n'
#define null NULL
#define ls p<<1
#define rs p<<1|1
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define ll long long
//#define int long long
#define pii pair<int,int>
#define ull unsigned long long
#define pdd pair<double,double>
#define lowbit(x) x&-x
#define all(x) (x).begin(),(x).end()
#define sz(x) (int)(x).size()
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
char *fs,*ft,buf[1<<20];
#define gc() (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<20,stdin),fs==ft))?0:*fs++;
inline int read()
{
    
    
    int x=0,f=1;
    char ch=gc();
    while(ch<'0'||ch>'9')
    {
    
    
        if(ch=='-')
            f=-1;
        ch=gc();
    }
    while(ch>='0'&&ch<='9')
    {
    
    
        x=x*10+ch-'0';
        ch=gc();
    }
    return x*f;
}
using namespace std;
const int N=150000+1500;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const double eps=1e-6;
const double PI=acos(-1);
int a[N],b[N],c[N];
ll f[2][N],q[N];
signed main()
{
    
    
    int n=read(),m=read(),d=read();
    for(int i=1; i<=m; i++)
    {
    
    
        a[i]=read();
        b[i]=read();
        c[i]=read();
    }
    int fg=0,t=c[1];
    for(int i=1; i<=m; i++)
    {
    
    
        int l=0,r=0,k=1;
        if(i==1)
        {
    
    
            for(int j=1; j<=n; j++)
                f[fg][j]=b[i]-abs(a[i]-j);//刚开始,直接给值
        }
        else
        {
    
    
            for(int j=1; j<=n; j++)
            {
    
    
                while(k<=n&&k<=j+1LL*(c[i]-c[i-1])*d)//k在合法范围内
                {
    
    
                    while(l<r&&f[fg^1][k]>=f[fg^1][q[r-1]])
                    //后面到的比前面的更大,就一定更优,直接插队就行了
                        r--;
                    q[r++]=k++;
                }
                while(l<r&&j-1LL*(c[i]-c[i-1])*d>q[l])
                //已经超出范围的无用信息就直接删去,最后队列首部就是当前最优的状态
                    l++;
                f[fg][j]=f[fg^1][q[l]]+b[i]-abs(a[i]-j);
            }
        }
        fg^=1;
    }
    ll res=-1e18;
    for(int i=1;i<=n;i++)
        res=max(res,f[fg^1][i]);
    cout<<res<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Joker_He/article/details/109302098
今日推荐