「JOISC 2017 Day 1」烟花棒

本文详细介绍了JOISC 2017第一天比赛中的烟花棒问题,描述了问题背景,即n个人在数轴上,每人手持燃烧时间为T秒的烟花,从编号k的人开始点燃。分析了求解过程中,如何通过二分答案和贪心策略来确定所有人瞬时速度的最大值最小可以是多少。文章还给出了具体的解决方案,包括预处理和使用ST表维护区间最值的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

有n个人站在数轴上,第i个人在Xi
每个人手上都有一个能燃烧T秒的烟花,刚开始时只有编号为k的人手上的烟花是燃着的
只允许用燃着的烟花去点燃未点燃的烟花,当且仅当两个人的位置重叠且一个人手上的烟花点燃时能点燃另一个人的烟花
问将所有人手上的烟花都点燃的过程中,所有人瞬时速度的最大值最小可以是多少
n<=10^5

Solution

考虑二分答案v,对于区间[i,j],最后一个人可能在的坐标区间是[xj-vT(j-i),xi+vT(j-i)],如果满足xj-vT(j-i)<=xi+vT(j-i)则合法
令ai=xi-2vTi,那么条件转化为ai>=aj则区间[i,j]合法
现在问题变成了,刚开始在[k,k],每次可以往左或往右扩展一步,需要时刻保证区间合法,问能否走到[1,n]
考虑贪心,设当前在区间[x,y],如果存在i<x满足min(ai~ax)>=ay且ai>ax走过去显然不劣
右边同理,我们可以预处理出每个点左边第一个>a[x]和右边第一个<a[y]的点
然后用st表维护区间最值
如果不能走了的话只能暴力往两边走,注意中途不能跨过不合法的区间

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=1e5+5;
const ll inf=1e18;

int n,T,k,f[N][17],g[N][17],x[N],lg[N],sta[N],L[N],R[N],top;
ll b[N];

int get_mn(int l,int r) {
	if (l>r) return n+1;
	int z=lg[r-l+1];
	return b[f[l][z]]<b[f[r-(1<<z)+1][z]]?f[l][z]:f[r-(1<<z)+1][z];
}

int get_mx(int l,int r) {
	if (l>r) return 0;
	int z=lg[r-l+1];
	return b[g[l][z]]>b[g[r-(1<<z)+1][z]]?g[l][z]:g[r-(1<<z)+1][z];
}

bool check(ll lim) {
	fo(i,1,n) {
		b[i]=x[i]-(ll)i*lim*T*2;
		f[i][0]=g[i][0]=i;
	}
	fo(j,1,16)
		fo(i,1,n-(1<<j)+1) {
			f[i][j]=b[f[i][j-1]]<b[f[i+(1<<j-1)][j-1]]?f[i][j-1]:f[i+(1<<j-1)][j-1];
			g[i][j]=b[g[i][j-1]]>b[g[i+(1<<j-1)][j-1]]?g[i][j-1]:g[i+(1<<j-1)][j-1];
		}
	top=0;
	fo(i,1,n) {
		while (top&&b[sta[top]]<=b[i]) top--;
		L[i]=sta[top];sta[++top]=i;
	}
	top=0;
	fd(i,n,1) {
		while (top&&b[sta[top]]>=b[i]) top--;
		R[i]=sta[top];sta[++top]=i;
	}
	b[n+1]=inf;b[0]=-inf;
	for(int x=k,y=k;x>1||y<n;) {
		if (L[x]&&b[get_mn(L[x]+1,x-1)]>=b[y]) x=L[x];
		else if (R[y]&&b[get_mx(y+1,R[y])]<=b[x]) y=R[y];
		else {
			if (x==1) return b[get_mx(y+1,n)]<=b[x];
			if (y==n) return b[get_mn(1,x-1)]>=b[y];
			int l=get_mn(1,x-1),r=get_mx(y+1,n);
			if (b[l]<b[y]||b[r]>b[x]) return 0;
			int u=get_mx(1,l),v=get_mn(r,n);
			if (b[u]>=b[r]) x=u;
			else if (b[v]<=b[l]) y=v;
			else return 0;
		}
	}
	return 1;

}

int main() {
	n=read();k=read();T=read();
	fo(i,1,n) x[i]=read();
	fo(i,2,n) lg[i]=lg[i>>1]+1;
	int l=0,r=(x[n]-x[1])/(2*T)+1;
	while (l<r) {
		int mid=l+r>>1;
		if (check(mid)) r=mid;
		else l=mid+1;
	}
	printf("%d\n",l);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值