题目
有一条街,这条街可以被分成n节,记为1,2,3……,n。同时有若干烟花要在街上放,每个烟花都有自己放的时间和幸福值和最佳观看地点(街上的哪一节),烟花时你获得的幸福值就是该烟花的幸福值减去你所站的位置离最佳观看位置的距离,你每秒最多能移动d个单位,求所有烟花放完后你的幸福值最大可以是多少。
思路
我们可以知道,直接解决整个问题是很难的,但是如果我们考虑中间放的第i个烟花,此时要取得最大的幸福值,只需要枚举前i-1个烟花放完后站在街上的每一节的幸福值最大值进行计算,再找出这些计算结果的最大值就可以了,于是就化为了最优子结构,想到运用dp来解题,dp[i][j]表示放完第i个烟花时在第j节的最大幸福值,状态转移方程为dp[i][j]=max(dp[i][j],dp[i-1][k]+b[i]-abs(a[i]-j))【j-d*(t[i]-t[i-1])<=k<=j+d*(t[i]-t[i-1])】,当然j-d*(t[i]-t[i-1])和j-d*(t[i]-t[i-1])不能超出界限,但是直接这样做会tle,我们还需要优化这种做法。我们注意到对于每个dp[i][j],b[i]-abs(a[i]-j)是不变的,要找出其最大值,就是让dp[i-1][k]最大,然后k明显是有一个范围,因此不难看出可以使用单调队列进行优化,具体实现上,因为可以从j往右走,也可以从j往左走,所以我们进行两次滑动窗口,一次往右,一次往左,这道题就解决了。
代码
#include <iostream>
#include <queue>
#include <cmath>
#include <cstring>
using namespace std;
#define int long long
const int inf=1e18;
typedef struct pa{
int idx,num;
}pa;
int a[305],b[305],t[305];
int dp[2][150005];
deque<pa>q;
int max(int a,int b){
return a>b?a:b;
}
signed main(){
int n,m,d;cin>>n>>m>>d;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i]>>t[i];
}
int flag=1;
for(int i=1;i<=m;i++,flag=!flag){
for(int j=1;j<=n;j++){
while(!q.empty()&&q.front().idx<j-d*(t[i]-t[i-1]))q.pop_front();
while(!q.empty()&&q.back().num<=dp[!flag][j])q.pop_back();
pa temp;temp.idx=j;temp.num=dp[!flag][j];q.push_back(temp);
dp[flag][j]=q.front().num+b[i]-fabs(j-a[i]);
}
q.clear();
for(int j=n;j>=1;j--){
while(!q.empty()&&q.front().idx>j+d*(t[i]-t[i-1]))q.pop_front();
while(!q.empty()&&q.back().num<=dp[!flag][j])q.pop_back();
pa temp;temp.idx=j;temp.num=dp[!flag][j];q.push_back(temp);
dp[flag][j]=max(q.front().num+b[i]-fabs(j-a[i]),dp[flag][j]);
}
q.clear();
}
int ans = -inf;
for(int i=1;i<=n;i++)ans=max(ans,dp[!flag][i]);
cout<<ans;
}