CF372C Watching Fireworks is Fun

CF372C Watching Fireworks is Fun

题意:

城镇中有 \(n\) 个位置,有 \(m\) 个烟花要放。第 \(i\) 个烟花放出的时间记为 \(t_i\) ,放出的位置记为 \(a_i\) 。如果烟花放出的时候,你处在位置 \(x\),那么你将收获 \(b_i-|a_i-x|\) 点快乐值。

初始可以在任意位置,每个单位时间可以移动不大于 \(d\) 个单位距离。

分析。

通过题意,可以很自然写出转移方程:设 \(dp[i][j]\) 表示在第 \(i\) 个烟花时在第 \(j\) 个地方。

则有转移方程:

\[dp[i][j]=min(dp[i][j],dp[i-1][k]+b_i-|a_i-x|); \]

其中 \(k∈[j-(t[i]-t[i-1])*d,j+(t[i]-t[i-1])*d]\)

感觉是不是空间和时间都有点多。。。。

考虑优化:

  1. 滚动数组第一维,开到 \(2\) 就行。

  2. \(b_i-|a_i-x|\) 求最大值,同时因为 \(b_i\) 为固定值,我们求 \(|a_i-x|\) 最小值即可。

  3. 枚举 \([1,m]\)\([1,n]\) 是无法优化的,所以我们思考能否优化 \(k\) 的遍历。

我们需要在 \(k\) 的范围内最小值来更新现在的 \(dp[i][j]\) .

用单调队列优化,因为 \(k\) 左右两边都需要拓展,所以从 \([1,n]\)\([n,1]\) 跑一遍,更新 \(dp[i][j]\) 最小值即可。

代码:

#include<bits/stdc++.h>
using namespace std;

#define int long long 
const int N=150005,M=305;
int n,m,d,sum,ans=INT_MAX/2;
int a[N],b[N],t[N],dp[2][N],q[N];

signed main(){
    cin>>n>>m>>d; memset(dp,0x3f3f3f3f,sizeof(dp));
    for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&a[i],&b[i],&t[i]),sum+=b[i];//算减法就行
    for(int i=1;i<=n;i++) dp[1][i]=abs(a[1]-i);//初始化
    for(int i=2;i<=m;i++){
        int now=i&1,last=i&1^1,ti=t[i]-t[i-1];
        memset(dp[now],0x3f3f3f3f,sizeof(dp[now]));
        int head=1,tail=0;
        for(int j=1;j<=n;j++){
            while(head<=tail&&q[head]<j-ti*d) ++head;//计算是否已经不能选取,就跳出
            while(head<=tail&&dp[last][q[tail]]>dp[last][j]) --tail;//更往后,也更优秀
            q[++tail]=j;
            dp[now][j]=min(dp[now][j],dp[last][q[head]]+abs(a[i]-j));
        }		
        head=1,tail=0;
        //因为k两边都有,因此需要左右两边分别算
        for(int j=n;j>=1;j--){
            while(head<=tail&&q[head]>j+ti*d) ++head;
            while(head<=tail&&dp[last][q[tail]]>dp[last][j]) --tail;
            q[++tail]=j;
            dp[now][j]=min(dp[now][j],dp[last][q[head]]+abs(a[i]-j));
        }	
    }
    for(int i=1;i<=n;i++) ans=min(dp[m&1][i],ans);
    cout<<sum-ans<<endl;
    // system("pause");
    return 0;
}
posted @ 2021-09-26 17:17  Evitagen  阅读(46)  评论(0编辑  收藏  举报