[CodeForces 372C] Watching Fireworks is Fun (DP+单调队列优化)

题面
在这里插入图片描述大意
在一条街道上有 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 = &lt; x &lt; = n ) x(1=&lt;x&lt;=n) x1=<x<=n)位置上获得的快乐值为 b i b_i bi- ∣ a i − x ∣ \vert a_i-x\vert aix,刚开始你可以在任意位置,之后可以移动你的位置,每秒可移动 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[i1][k])+biaik
其中 m a x ( j − ( t i − t i − 1 ) ∗ d , 1 ) &lt; = k &lt; = m i n ( j + ( t i − t i − 1 ) ∗ d , n ) max(j-(t_i-t_{i-1})*d,1)&lt;=k&lt;=min(j+(t_i-t_{i-1})*d,n) max(j(titi1)d,1)<=k<=min(j+(titi1)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(titi1)d,1)枚举到 m i n ( j + ( t i − t i − 1 ) ∗ d , n ) min(j+(t_i-t_{i-1})*d,n) min(j+(titi1)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[i1][1] dp[i1][titi1d+1]构造单调队列(显然是单调递减的),然后每次计算最大值时就可以直接取用队首节点的值,计算完后检查一下队首节点是否对下一个节点有用(在区间内),如果无用则出队,同时将可用的新增节点入队,重复若干步即可。

但是这题还有一个坑
这题的范围 n &lt; = 150000 n&lt;=150000 n<=150000, m &lt; = 300 m&lt;=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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值