题目
题目描述
题目译自 JOISC 2017 Day1 T3「手持ち花火 / Sparklers」
有
N
N
N 人站在一条数轴上。他们人手一个烟花,每人手中的烟花都恰好能燃烧
T
T
T 秒。每个烟花只能被点燃一次。
1
1
1 号站在原点,
i
i
i 号
(
1
≤
i
≤
n
)
(1\leq i\leq n)
(1≤i≤n) 到
1
1
1 号的距离为 。保证
X
1
=
0
X_1 = 0
X1=0,
X
1
,
X
2
,
.
.
.
,
X
N
X_1,X_2,...,X_N
X1,X2,...,XN 单调不降(可能有人位置重叠)。
开始时,
K
K
K 号的烟花刚开始燃烧,其他人的烟花均未点燃。他们的点火工具坏了,只能用燃着的烟花将未点燃的烟花点燃。当两人位置重叠且其中一人手中的烟花燃着时,另一人手中的烟花就可以被点燃。忽略点火所需时间。
求至少需要以多快的速度跑,才能点燃所有人的烟花(此时可能有些人的烟花已经熄灭了)。速度必须是一个非负整数。
输入格式
第一行有三个整数
N
,
K
,
T
N,K,T
N,K,T,用空格分隔。
在接下来的
N
N
N 行中,第
i
i
i 行
(
1
≤
i
≤
N
)
(1 \leq i \leq N)
(1≤i≤N) 有一个整数
X
i
X_i
Xi。
输出格式
一个整数,表示要想点燃所有人的烟花,全程中最大速度的最小值。
样例
样例输入 1
3 2 50
0
200
300
样例输出 1
2
样例解释 1
开始时,
1
1
1 号向右,
2
2
2 号向左,
3
3
3 号向左。
50
50
50 秒后,
2
2
2 号传火给
1
1
1 号。随后,
1
1
1 号和
3
3
3 号继续移动。
又过了
25
25
25 秒,
1
1
1 号传火给
3
3
3 号。
样例输入 2
3 2 10
0
200
300
样例输出 2
8
样例解释 2
开始时,
1
1
1 号向右,
2
2
2 号向右,
3
3
3 号向左。
3
3
3 秒后,
2
2
2 号停止移动。
又过了
6.5
6.5
6.5 秒,
3
3
3 号到达
2
2
2 号所在位置,
3
3
3 号停止移动。
又过了
0.5
0.5
0.5 秒,
2
2
2 号传火给
3
3
3 号。
又过了
9
9
9 秒,
3
3
3 号传火给
1
1
1 号。
样例输入 3
20 6 1
0
2
13
27
35
46
63
74
80
88
100
101
109
110
119
138
139
154
172
192
样例输出 3
6
数据范围与提示
对于
30
%
30\%
30% 的数据,
N
≤
20
N \le 20
N≤20
对于
50
%
50\%
50% 的数据,
N
≤
1000
N \le 1000
N≤1000
对于
100
%
100\%
100% 的数据,
1
≤
K
,
N
≤
1
0
5
,
1
≤
T
≤
1
0
9
,
0
≤
X
i
≤
1
0
9
(
1
≤
i
≤
N
)
,
X
1
=
0
,
{
X
N
}
1\le K, N \le 10^5, 1\le T\le 10^9, 0\le X_i\le 10^9 (1\le i\le N), X_1 = 0, \{X_N\}
1≤K,N≤105,1≤T≤109,0≤Xi≤109(1≤i≤N),X1=0,{XN} 单调递增。
思路
非常玄妙的一道题。
算法非常显然,答案具有可二分性,于是考虑二分答案,所以我们只需要判断当速度为 v v v 时是否可以点燃所有烟花棒,关键就在于我们如何写这样的一个 O ( n ) O(n) O(n) 的 check 函数。
很显然,每个人都应该往手里有火的那个人的方向跑。注意到两人相遇后不用立即传火。现在,我们给定一个很重要的性质:每个人遇到手上有火的那个人之后,如果火还没灭,那么他一定会跟着手上有火的人,知道另外一个人手上的火熄灭,此时再传给这个手上没有火的人。
下面,我们证明,立即传火是不优的:
首先,如果两个人传火后,移动方向相同,显然不传火一定更优,所以只需考虑两人相反移动的情况。
不妨令原来手上有火的那个人向右走,另一个人向左走。
考虑两人相遇后立即传火。假设他们相遇的点在 x x x,手上的火还剩 t t t 秒,那么他们能够扩展的区间为: [ x − T v , x + c v ] [x-Tv, x+cv] [x−Tv,x+cv]( v v v 为速度, T T T 见题目描述,为燃烧时间)。
而如果两个人相遇后不传火,那么他们能够扩展的区间为: [ x + c v − T v , x + c v ] [x+cv-Tv,x+cv] [x+cv−Tv,x+cv],即 [ x − ( T − c ) v , x + c v ] [x-(T-c)v, x+cv] [x−(T−c)v,x+cv],看似比上面扩展的区间小,但其实其他点也跟着一起运动了 c c c 秒,而向左扩展的区间少了 c c c 秒,根据相对运动,他们的距离和上面没有变化,由此得证。
于是,我们得到了很重要的一个结论:如果两个人相遇,我们就把其视作火把燃烧时间延伸了 T T T 秒。
我们还发现,由于每个人的速度相同,所以在移动方向相同时,两个人之间的距离和相对位置是不会发生改变的,只有两个人相向而行时,距离才会变小,且花费时间不会受之前的影响,于是我们可以将问题转化。
我们记录两个数组 a , b a,b a,b,其中 a i = X i + 1 − X i 2 × v , ( i < k ) a_i = \frac{X_{i+1}-X_i}{2\times v}, (i < k) ai=2×vXi+1−Xi,(i<k), b i = X i − X i − 1 2 × v , ( i > k ) b_i= \frac{X_i-X_{i-1}}{2\times v},(i>k) bi=2×vXi−Xi−1,(i>k)。
现在,我们要删除这两个数组。两个数组内部要有序删除,其中, a a a 数组要按照 k − 1 ∼ 1 k-1\sim 1 k−1∼1 的顺序删除, b b b 数组要按照 k + 1 ∼ n k+1\sim n k+1∼n 的顺序删除。每次删掉一个 a i a_i ai 或 b i b_i bi 后,就会先减少 a i a_i ai 或 b i b_i bi 的时间,并获得 T T T 的时间,要求最后的剩余时间非负。
贪心的选择当前收益更大的策略显然是错误的。我们考虑一直删除一个数组,直到收益非负时,就可以收手了。也就是说,我们把数组分成若干段,每一段任意前缀的收益都为负,只有整组都删的收益为非负(这里 a a a 数组 k − 1 k-1 k−1 为前, 1 1 1 为后)。显然,我们每次删都会删掉一整组数,而非在中途改变方向。对于每一个组,我们可以用一个二元组 ( p , v ) (p,v) (p,v) 表示删完这一个组需要 p p p 秒,可以获得 v v v 秒时间。(维护方法详见代码)
就这样,我们将两个数组划分成了若干个组,我们每次贪心的去选择 p p p 最小的那个组(当然,组间顺序不能忽略),如果当前时间已经 < p < p <p 了,那就不合法了,否则删完这个组,获得 v v v 秒时间。
此时,我们注意到,一个数组并不一定会完全被划分为这若干个组,可能会有一段后缀剩余。这该怎么办呢?
不难发现,最终剩余的时间是固定的,为 ∑ i = 1 t o t a ( − a i + T ) + ∑ i = 1 t o t b ( − b i + T ) \sum \limits_{i=1}^{tota}(-a_i+T)+\sum \limits_{i=1}^{totb}(-b_i+T) i=1∑tota(−ai+T)+i=1∑totb(−bi+T),其中 t o t a tota tota 和 t o t b totb totb 分别代表 a a a, b b b 数组的长度。那么,我们只需将其翻转,进行时光倒流。此时,删掉一个数就相当于先减去 T T T 秒,在加上 a i a_i ai 或 b i b_i bi。于是我们可以类似上面的方法,将剩下的这些数也分成若干个组,每个组也用一个二元组 ( p , v ) (p,v) (p,v) 表示删完这一个组需要先消耗 p p p 秒,可以加回 v v v 秒时间。(维护方法几乎相同,详见代码)。
依旧是保证任意时刻时间非负,每次贪心选 p p p 小的。
时间复杂度 O ( n l o g v ) O(n \;log\;v) O(nlogv)。
注意,注意,注意,如果你在洛谷(即atcoder)上提交这一题,输出一定要加上一个换行,atcoder 评测不是忽略行末换行!!!(坑了我十几发提交)
代码
#include <bits/stdc++.h>
#define rep(i, a, b) for (register int i(a); i <= b; ++i)
#define per(i, a, b) for (register int i(a); i >= b; --i)
#define FILE(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
#define mem(a, x) memset(a, x, sizeof a)
#define pb push_back
#define umap unordered_map
#define pqueue priority_queue
#define mp make_pair
#define PI acos(-1)
//#define int long long
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int n, k, t, x[100005];
double a[100005], b[100005];
struct node {
double p, v;
} A[100005], B[100005];
const double eps = 1e-12;
template <typename _T>
void rd(_T &x) {
int f = 1; x = 0;
char s = getchar();
while (s > '9' || s < '0') {if (s == '-') f = -1; s = getchar();}
while (s >= '0' && s <= '9') x = (x<<3)+(x<<1)+(s^48), s = getchar();
x *= f;
}
template <typename _T>
void write(_T x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x/10);
putchar(x%10+'0');
return ;
}
bool check(int v) {
int tota = 0, totb = 0;
per(i, k-1, 1) a[++tota] = ((double)(x[i+1]-x[i])/2./(double)(v));
rep(i, k+1, n) b[++totb] = ((double)(x[i]-x[i-1])/2./(double)(v));
double now = 0., minn = 9e18;
int totA = 0, totB = 0;
int p = 0, q = 0;
rep(i, 1, tota) {
now -= a[i];
minn = min(minn, now);
now += (double)(t);
if (now > -eps) A[++totA] = (node){-minn, now}, now = 0., minn = 9e18, p = i;
}
now = 0., minn = 9e18;
rep(i, 1, totb) {
now -= b[i];
minn = min(minn, now);
now += (double)(t);
if (now > -eps) B[++totB] = (node){-minn, now}, now = 0., minn = 9e18, q = i;
}
int p1 = 1, p2 = 1;
now = (double)(t);
while (p1 <= totA || p2 <= totB) {
if (p1 <= totA && (now-A[p1].p) > -eps) now += A[p1++].v;
else if (p2 <= totB && (now-B[p2].p) > -eps) now += B[p2++].v;
else return false;
}
totA = totB = 0;
now = 0., minn = 9e18;
per(i, tota, p+1) {
now -= (double)(t);
minn = min(minn, now);
now += a[i];
if (now > -eps) A[++totA] = (node){-minn, now}, now = 0., minn = 9e18;
}
now = 0., minn = 9e18;
per(i, totb, q+1) {
now -= (double)(t);
minn = min(minn, now);
now += b[i];
if (now > -eps) B[++totB] = (node){-minn, now}, now = 0., minn = 9e18;
}
now = (double)(t);
rep(i, 1, tota) now -= a[i], now += (double)(t);
rep(i, 1, totb) now -= b[i], now += (double)(t);
if (now < 0) return false;
p1 = p2 = 1;
while (p1 <= totA || p2 <= totB) {
if (p1 <= totA && (now-A[p1].p) > -eps) now += A[p1++].v;
else if (p2 <= totB && (now-B[p2].p) > -eps) now += B[p2++].v;
else return false;
}
return true;
}
int main() {
rd(n), rd(k), rd(t);
rep(i, 1, n) rd(x[i]);
int l = 0, r = 1e9, ans = 0;
while (l <= r) {
int mid = (l+r) >> 1;
if (check(mid)) ans = mid, r = mid-1;
else l = mid+1;
}
printf("%d", ans);
return 0;
}