PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接:洛谷 P4697 [CEOI2011] Balloons
题目
题目描述
有 n 个气球,他们一开始都是空的。
接下来,它们会按照从 1 到 n 的顺序依次充气,其中第 i 个气球与地面在 x[i] 位置接触。
当气球碰到碰到前面的某个气球,或者达到半径最大限制时,就会停止充气。其中第 i 个气球的半径最大限制为 r[i]。
现在请你求出,每个气球最终半径是多少。
输入格式
第一行一个正整数 n,表示气球个数。
接下来 n 行,每行两个空格隔开的整数 x[i],r[i]。
输出格式
输出 n 行,每行一个浮点数,第 i 行的浮点数表示最终第 i 个气球的半径。
你的答案会被判为正确,当且仅当与答案的绝对误差不超过 10^(-3) 。
样例
样例输入
3
0 9
8 1
13 7
样例输出
9.000
1.000
4.694
提示
对于 100% 的数据,保证 1≤n≤200 000 ; 0≤x[i]≤10^9 ; 1≤r[i]≤10^9 ; x[1]< x[2]< …< x[n] 。
题解
暴力解法
首先,我们要判断什么时候气球会接触。
假设前一个气球的半径长为r1(已知),后一个气球半径长为r2,两气球与地面接触位置的距离差为Δx。
显然地,当这两个气球接触时,可得:
化简可得
所以只需要按顺序在求每一个气球大小的时候循环与前面每一个气球半径进行判断就可以了。
这个做法效率大概是 (有可能算错了),一旦气球多了就会超时。
单调栈优化
当然,是个聪明人就会仔细阅读提示发现位置是单调递增的(我是傻子),所以可以考虑用单调栈来求解。
对于单调栈中的元素 i<j 而言,假设一个气球编号 k>j>i ,若 r[i]<r[j] ,则显然气球 k 的半径长度不受 i 的影响。(不明白为什么的可以自己画一下图)
那么我们可以用维持一个递减的单调栈(即会影响后面气球半径的气球编号):
- 取出栈顶 j
- 如果 r[k]<r[j] ,就加入栈顶并break
- 如果 r[k]>r[j] ,就弹出栈顶 j 并重复上述操作
代码实现
//洛谷 P4697 [CEOI2011] Balloons
#include<iostream>
#include<cstdio>
using namespace std;
int n;
long double x[200010];
long double r[200010];
int stk[200010];
int top;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%LF%LF",&x[i],&r[i]);
}
for(int i=1;i<=n;++i){
while(top>0){
int j=stk[top];
r[i]=min(r[i],(x[i]-x[j])*(x[i]-x[j])/(4.0*r[j]));
if(r[i]<=r[j]){
break;
}
top--;
}
stk[++top]=i;
printf("%.3LF\n",r[i]);
}
return 0;
}