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; }
实际上我们只需要维护一下$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; }