1. 程式人生 > 其它 >【題解】JOISC2017 手持ち花火(Sparklers) | 20211106 模擬賽 你還沒有導光嗎(light)【二分 貪心】

【題解】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;
}


若文章內無特別說明,公開文章採用知識共享署名-相同方式共享 4.0 國際許可協議進行許可。