题意
数轴上有 \(n\) 个人,每人手持一根烟花棒,一根烟花棒只能被点燃一次,燃烧 \(t\) 秒。初始第 \(k\) 个人手上的烟花棒刚刚点燃,随后所有人开始移动;当两人位置重叠且其中一个人的烟花棒还在燃烧时,一个人可以用自己的烟花棒点燃另一个的,耗时不计。现在希望每个人的烟花棒都被点燃过,问全程所有人最大速度的最小值。\(n\leq 10^5\)
题解
首先二分。我们注意到以下性质:
- 一个人其实可以一直全速奔跑;
- 如果有两人同时有燃着的烟花棒,其实可以让一个人在烟花棒燃尽时再点燃另一个的,因此同一时间只有一个烟花棒被点燃;
- 所有人都往烟花棒所在位置跑,大部分人的相对位置不变。
因此我们需要做若干次决策,形如当前烟花棒燃着的人(或者说与他位置重叠的一群人)往左跑还是往右跑,一次决策会让烟花燃烧的剩余时间减一定值再加 \(t\)。
把往左往右的决策放入两个队列,每次我们会把队首尽可能短但总收益为正的一段合并起来,在左右之间任选一个可行的这样的段。如果这样的段清空不了则不合法。还有些元素没有被合并到段里,我们倒着做这个过程(即先算出结束时烟花棒的剩余时间,再把整个过程逆序做一遍)。
#include<bits/stdc++.h>
using namespace std;
vector<int>vl,vr;
int n,k,t;
double len;
pair<vector<pair<double,double>>,vector<pair<double,double>>> split(vector<int>v,double x){
vector<pair<double,double>>vl;
double co=0,get=0;
int u=-1;
for(int i=0;i<v.size();i++){
get-=v[i]/x/2;
co=max(co,-get);
get+=t;
if(get>=0){
vl.emplace_back(co,get);
u=i;
co=0;get=0;
}
}
co=0;get=0;
vector<pair<double,double>>vr;
int uu=v.size();
for(int i=v.size()-1;i>u;--i){
get-=t;
co=max(co,-get);
get+=v[i]/x/2;
if(get>=0){
vr.emplace_back(co,get);
uu=i;
co=0;get=0;
}
}
if(uu!=u+1)return {{{1e9,0}},{{1e9,0}}};
return make_pair(vl,vr);
}
bool check(vector<pair<double,double>>v1,vector<pair<double,double>>v2,double t0){
double cur=t0;
int u=0,v=0;
while(u<v1.size()||v<v2.size()){
if(u<v1.size()&&cur>=v1[u].first)cur+=v1[u++].second;
else if(v<v2.size()&&cur>=v2[v].first)cur+=v2[v++].second;
else return false;
}
return true;
}
bool check(double x){
auto vll=split(vl,x);
auto vrr=split(vr,x);
return check(vll.first,vrr.first,t)&&check(vll.second,vrr.second,t*1.*n-len/x/2);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>k>>t;
int lst=0;cin>>lst;
for(int i=1;i<k;i++){
int x;cin>>x;
vl.push_back(x-lst);
lst=x;
}
for(int i=k;i<n;i++){
int x;cin>>x;
vr.push_back(x-lst);
lst=x;
}
len=lst;
if(lst==0)return cout<<0,0;
reverse(vl.begin(),vl.end());
int l=0,r=1e9,ans=0;
while(l<=r){
int mid=(l+r)/2;
if(check(mid))r=mid-1,ans=mid;
else l=mid+1;
}
cout<<ans<<endl;
}