【题解】JOISC2017 手持ち花火(Sparklers) | 20211106 模拟赛 你还没有导光吗(light)【二分 贪心】

题目链接

题目链接

题意

数轴上有 \(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;
}

posted @ 2021-11-06 19:52  破壁人五号  阅读(108)  评论(0编辑  收藏  举报