题面
大意
在一条街道上有
n
n
n个位置分别为
1
1
1~
n
n
n,有
m
m
m个烟花,给出每个烟花的位置
a
i
a_i
ai和燃放的时间
t
i
t_i
ti,以及
b
i
b_i
bi值,当烟花燃放时,你在街道的
x
(
1
=
<
x
<
=
n
)
x(1=<x<=n)
x(1=<x<=n)位置上获得的快乐值为
b
i
b_i
bi-
∣
a
i
−
x
∣
\vert a_i-x\vert
∣ai−x∣,刚开始你可以在任意位置,之后可以移动你的位置,每秒可移动
d
d
d个单位,求所有烟花燃放完之后的快乐值最大值。
思路
显然这题如果用
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示放第
i
i
i个烟花时在
j
j
j位置能得到的最大快乐值,那么可以得到状态转移方程
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
k
]
)
+
b
i
−
∣
a
i
−
k
∣
dp[i][j]=max(dp[i-1][k])+b_i-\vert a_i-k\vert
dp[i][j]=max(dp[i−1][k])+bi−∣ai−k∣
其中
m
a
x
(
j
−
(
t
i
−
t
i
−
1
)
∗
d
,
1
)
<
=
k
<
=
m
i
n
(
j
+
(
t
i
−
t
i
−
1
)
∗
d
,
n
)
max(j-(t_i-t_{i-1})*d,1)<=k<=min(j+(t_i-t_{i-1})*d,n)
max(j−(ti−ti−1)∗d,1)<=k<=min(j+(ti−ti−1)∗d,n)
但是如果每次计算
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]时都从
m
a
x
(
j
−
(
t
i
−
t
i
−
1
)
∗
d
,
1
)
max(j-(t_i-t_{i-1})*d,1)
max(j−(ti−ti−1)∗d,1)枚举到
m
i
n
(
j
+
(
t
i
−
t
i
−
1
)
∗
d
,
n
)
min(j+(t_i-t_{i-1})*d,n)
min(j+(ti−ti−1)∗d,n)的话时间复杂度是
O
(
n
m
d
)
O(nmd)
O(nmd)的(考虑最坏情况
d
=
n
d=n
d=n的话,时间复杂度是
O
(
m
n
2
)
O(mn^2)
O(mn2))
但是考虑到每次都从前一次的结果中连续的一段区间取,而且这个连续的区间是随着当前枚举的
j
j
j移动而移动的,这个时候就可以使用单调队列优化。
具体来说,先用
d
p
[
i
−
1
]
[
1
]
d
p
[
i
−
1
]
[
(
t
i
−
t
i
−
1
)
∗
d
+
1
]
dp[i-1][1]~dp[i-1][(t_i-t_{i-1})*d+1]
dp[i−1][1] dp[i−1][(ti−ti−1)∗d+1]构造单调队列(显然是单调递减的),然后每次计算最大值时就可以直接取用队首节点的值,计算完后检查一下队首节点是否对下一个节点有用(在区间内),如果无用则出队,同时将可用的新增节点入队,重复若干步即可。
但是这题还有一个坑
这题的范围
n
<
=
150000
n<=150000
n<=150000,
m
<
=
300
m<=300
m<=300,然后dp的最大值显然是会超过int的,但是开long long的话又会MLE没想到吧! 所以我们需要用到滚动数组来避免爆空间。
AC代码
#include<stdio.h>
#include<deque>
#define M 305
#define N 150005
#include<algorithm>
using namespace std;
deque<int> q;
long long dp[2][N],a[M],b[M],t[M];
int n,m,d;
void cal(int now)
{
while(!q.empty()) q.pop_back();
long long temp=(t[now+1]-t[now])*(long long)d;
long long fal=min(temp+1,(long long)n),z=temp;
for(long long i=1;i<=fal;i++)//先将对于dp[i][1]有用的节点入队
{
while(!q.empty()&&dp[now%2][q.back()]<=dp[now%2][i])q.pop_back();
q.push_back(i);
}
long long i;
for(i=1;fal<n;i++)
{
dp[(now+1)%2][i]=dp[now%2][q.front()]+b[now+1]-abs(a[now+1]-i);
fal++;
while(!q.empty()&&dp[now%2][q.back()]<=dp[now%2][fal]) q.pop_back();
q.push_back(fal);//将当前节点入队
if(i-z==q.front()) q.pop_front();//检查队首节点是否仍满足下一节点的范围
}
//所有节点都已入队过
for(;i<=n;i++)
{
dp[(now+1)%2][i]=dp[now%2][q.front()]+b[now+1]-abs(a[now+1]-i);
if(i-z==q.front()) //检查队首节点是否仍满足下一节点的范围
q.pop_front();
}
return;
}
void printans()
{
long long max=-1e17;
for(int i=1;i<=n;i++)if(dp[m%2][i]>max)max=dp[m%2][i];
printf("%I64d\n",max);
return;
}
int main()
{
scanf("%d%d%d",&n,&m,&d);
for(int i=1;i<=m;i++)
{
scanf("%I64d%I64d%I64d",&a[i],&b[i],&t[i]);
}
t[0]=1;
for(int i=0;i<m;i++)
cal(i);
printans();
return 0;
}