题目大意:
给出N个位子,排成一列,现在有M个烟花,每秒人的速度d,每个烟花有三个元素:a,b,t,表示在时刻t会绽放,如果我们在位子x观看到了的话,会获得b-abs(a-x)的欢乐值(可能为负而且所有烟花都必须观看);初始的时候可以在任何位子,问最高获得多少欢乐值。
思路:
①观察到m并不大,所以t的数量也就是有限制的,考虑求最优方案,那么不难想到Dp去做,设定Dp【i】【j】表示在位子i,时刻j的时候获得的最优的欢乐值。
那么我们不难想到其状态转移方程:
Dp【i】【j】=Dp【x】【j-1】(x和i之间的差距要在时间内走的到才能转移)+当前时刻能够获得的价值summ;
那么我们不难想到用线段树加速优化,但是这道题卡了Log.........................
观察到转移的时候,Dp【i】【j】转移新获得的价值只和当前位子j有关,所以我们能够用单调队列优化。
②对于单调队列能够确定决策单调性,但是两个方向不能同时进行,所以我们分两个方向两次去做就行了。
数组LL太大,爆内存,要用滚动数组优化= =
Ac代码:
#include<stdio.h>
#include<string.h>
#include<vector>
#include<map>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll __int64
vector< pair<ll,ll> >mp[305];
ll n,m,d;
ll dp[3][150060];
ll que[150060];
int main()
{
while(~scanf("%I64d%I64d%I64d",&n,&m,&d))
{
ll cnt=0;
map<ll,ll>s;s.clear();
map<ll,ll>re;re.clear();
for(ll i=1;i<=m;i++)mp[i].clear();
for(ll i=1;i<=m;i++)
{
ll a,b,t;
scanf("%I64d%I64d%I64d",&a,&b,&t);
if(s[t]==0)s[t]=++cnt;
re[s[t]]=t;
mp[s[t]].push_back(make_pair(a,b));
}
memset(dp,0,sizeof(dp));
for(ll i=1;i<=cnt;i++)
{
ll ttt=re[i]-re[i-1];
ll move=ttt*d;
ll tot,head;
head=tot=0;
que[tot]=1;
for(ll j=1;j<=n;j++)
{
ll summ=0;
for(ll k=0;k<mp[i].size();k++)
{
pair<ll,ll> temp=mp[i][k];
summ+=temp.second-abs(temp.first-j);
}
while(head<=tot&&abs(que[head]-j)>move)head++;
dp[1][j]=dp[0][que[head]]+summ;
while(j+1<=n&&head<=tot&&dp[0][que[head]]<=dp[0][j+1])tot--;
if(j+1<=n)que[++tot]=j+1;
}
head=tot=0;
que[tot]=n;
for(ll j=n;j>=1;j--)
{
ll summ=0;
for(ll k=0;k<mp[i].size();k++)
{
pair<ll,ll> temp=mp[i][k];
summ+=temp.second-abs(temp.first-j);
}
while(head<=tot&&abs(que[head]-j)>move)head++;
dp[1][j]=max(dp[1][j],dp[0][que[head]]+summ);
while(j-1>=1&&head<=tot&&dp[0][que[head]]<dp[0][j-1])tot--;
if(j-1>=1)que[++tot]=j-1;
}
for(ll j=1;j<=n;j++)dp[0][j]=dp[1][j];
}
ll output=-10000000000000000ll;
for(ll j=1;j<=n;j++)output=max(output,dp[0][j]);
printf("%I64d\n",output);
}
}