题意(转):有一个街道放烟花,街道分成 N 块(1 <= N <= 150000),相邻块距离为1, 你移动速度是 d 。烟花放 M 次 (1 <= M <= 300), 给出每次放烟花的时间地点,Happy 值得计算公式 b[i] - abs(a[i] - x) ,(b[i] 第i次放烟花的基准happy, a[i] 是放烟花的地点,x 是你所在的地点) 。求 Happy 值总和最大是多少。
http://codeforces.com/problemset/problem/372/C
dp[i][j]表示当前放到了第I支烟花并且放这支烟花的时候他站在j点看。推出状态转移方程:
dp[ i ] [ j ] =max(dp[ i - 1] [ k ]) + b[ i ] - | a[ i ] - j | ,其中 max(1,j-t*d)<=k<=min(n,j+t*d) 。不过这样会爆数组和时间。怎么办?
发现大神是用单调队列作辅助的。dp[ i ] [ j ] =?????+ b[ i ] - | a[ i ] - j | ,只要?????最大那么dp[i][j]就是最优解。由于相邻的j之间能取到的上一个状态k的范围[j-d,j+d]会有重叠部分,因此可以用双端队列进行优化。
#include<bits/stdc++.h>
#define mp make_pair
#define aa first
#define bb second
#define ll long long
#define pii pair<long long,long long>
using namespace std;
deque<pii> q;
ll dp[2][150005];
int main(){
ll m,n,k,a,b,t;
cin>>n>>m>>k;
ll tt=0;
int s1=0,s2=1;
for(int ii=1;ii<=m;++ii){
q.clear();
cin>>a>>b>>t;
ll d=k*(t-tt); tt=t;
ll i;
for(i=1;abs(i-1)<=d&&i<=n;++i){
while(!q.empty()&&q.back().aa<=dp[s1][i]) //维护的是从大到小的序列
q.pop_back();
q.push_back(mp(dp[s1][i],i));
}
for(int j=1;j<=n;++j){
while(abs(q.front().bb-j)>d)
q.pop_front();
dp[s2][j]=q.front().aa+b-abs(a-j);
if(i<=n){
while(!q.empty()&&q.back().aa<=dp[s1][i]) //维护的是从大到小的序列
q.pop_back();
q.push_back(mp(dp[s1][i],i));
i++;
}
}
swap(s1,s2);
}
ll g=dp[s1][1];
for(int i=2;i<=n;++i){
if(dp[s1][i]>g)
g=dp[s1][i];
}
cout<<g<<endl;
return 0;
}