CF372C Watching Fireworks is Fun 题解

本文迁移自洛谷原文

CF372C Watching Fireworks is Fun

我们可以换种思考方式来解决这个问题。

显然 是一个很无关紧要的东西,我们可以把它们先全部累加到 里。然后题目就变成了要让 最大,所以就可以看为要使得 最小。

表示考虑到某个时间点,此时你的坐标为 的最小的

假设我们已经考虑了前 场烟花,此时我们要考虑加入第 场烟花带来的影响。

,则我们就相当于执行

然后每燃放一场烟花,我们就相当于对 加上一个 的函数。

综上,可以发现,这个 是一个折线函数,且段数是 的,计算相邻时间和加入新烟花秀都可以在 的时间内解决,故我们得到了一个时间复杂度为 的做法。

考虑继续优化。

发现这个 的每条折线的斜率从左往右可以看作依次为

这启发我们可以维护两个优先队列一样的东西,一个维护左半段斜率小于 的部分的转折点的 坐标,另一个维护右半段的东西。

我们考虑在这种维护方式下,等待时间和加入烟花秀各有什么影响。

由于这个 是先降后增的,所以这个等待一段时间就变得非常好处理。就是让 中的所有元素减去 中的所有元素加上 ,中间多出一段斜率为 的段。

当然,我们没有必要真的去减一遍,我们只需要维护两个全局减去/加上的 ,每次就让 ,然后再 中加入新元素的时候就加入

再考虑这个加入烟花的操作。

令左半段最右边的转折点横坐标为 ,右半段最左边的转折点横坐标为

此时就相当于 都多了一个转折点 ,都push。

此时 的最右边的转折点就成为了 ,且斜率为 的转折点消失了,但不妨碍我们维护,因为我们可以想象它和斜率为 的转折点重合了。

所以我们加入两边 ,然后把 加到 即可。

和上一种情况同理。

综上,时间复杂度 ,可以加强到 ,甚至比原题的单调队列优化dp好写的多。

Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,d,ans,tag,pre;
priority_queue<int>L;
priority_queue<int,vector<int>,greater<int> >R;
signed main(){
ios_base::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>d;
for(int i=1;i<=m;++i){
int a,b,t;cin>>a>>b>>t;
ans+=b;
if(i==1)L.push(a),R.push(a),pre=t;
else{
tag+=d*(t-pre);
int l=L.top()-tag,r=R.top()+tag;
if(a<l)L.pop(),L.push(a+tag),L.push(a+tag),R.push(l-tag),ans-=l-a;
else if(a>r)R.pop(),R.push(a-tag),R.push(a-tag),L.push(r+tag),ans-=a-r;
else L.push(a+tag),R.push(a-tag);
}
pre=t;
}
cout<<ans<<'\n';
}