洛谷P9066 [yLOI2023] 腐草为萤 题解
题意:
夜幕降临,在树林中的一条平直的小径上,萤火虫们受到夜晚的呼唤,纷纷外出行动。
将小径视作数轴,一开始,共计 $n$ 只萤火虫在数轴的一些整点上,从左到右依次标号为 $1 \sim n$,第 $i$ 只萤火虫的初始坐标为 $x_i$。每个萤火虫有不同的亮度值,$i$ 号萤火虫的亮度为 $a_i$。
在任意时刻,对任意存活的萤火虫 $i$,它会按如下规则飞行:
- 在当前仍存活的萤火虫中,找到与 $i$ 相邻的萤火虫(可能是一只或两只)中亮度最大的一只,记其编号为 $j$。如果 $a_i < a_j$,则 $i$ 会朝着 $j$ 飞行,否则 $i$ 留在原地。
- 这里两只萤火虫『相邻』的定义是:若两只萤火虫之间不存在任何仍存活的萤火虫,则它们相邻。
- 萤火虫飞行的速度均为每秒一个单位长度。
萤火虫生命短暂,当两只萤火虫相遇之时(即两个萤火虫的坐标相同时),亮度值较低的萤火虫将耗尽生命,在小径上消失。显然,最后只会剩余 $1$ 只萤火虫。对其余的每只萤火虫,请分别求出它们耗尽生命时的坐标。
输入格式:
第一行是一个整数,表示萤火虫数量 $n$。
第二行有 $n$ 个整数,第 $i$ 个整数表示标号为 $i$ 的萤火虫初始坐标 $x_i$。数据保证 $x_i$ 单调递增。
第三行有 $n$ 个整数,第 $i$ 个整数表示标号为 $i$ 的萤火虫的亮度值 $a_i$。数据保证亮度值互不相同。输出格式:
输出一行 $n$ 个以单个空格隔开的整数,第 $i$ 个整数表示编号为 $i$ 的萤火虫生命耗尽时的坐标。如果 $i$ 号萤火虫最后存活下来了,则第 $i$ 个数输出 0。
数据范围:
- 对于 $5\%$ 的数据,$n = 2$。
- 对于 $30\%$ 的数据,$n \leq 100$,$x_i \leq 200$。
- 对于 $60\%$ 的数据,$n \leq 10^3$。
- 另有 $5\%$ 的数据,满足特殊约定 A。
- 另有 $5\%$ 的数据,满足特殊约定 B。
- 对 $100\%$ 的数据,保证 $2 \leq n \leq 5 \times 10^5$,$1 \leq x_i \leq 10^9$,$1 \leq a_i \leq n$。且 $x_i < x_{i + 1}$,$a_i$ 是长度为 $n$ 的排列。
其中:
- 特殊约定 A:数列 $a$ 单调递增。
- 特殊约定 B:数列 $a$ 是单峰的,仅有一个极大值。即:存在 $p$ 满足 $1 \leq p < n$,使得 $a_1 \sim a_p$ 单调递增,$a_p \sim a_n$ 单调递减.
省流:
数轴上有 $n$ 个点, 每个点的初始坐标为 $x_i$, 权重为 $a_i$ 。在任何时刻, 每个点会向与它相邻的两个点中权重较大的那个点移动, 速度为一个单位长度每秒, 如果相邻两个点的权重都比它自身权重小, 则不移动。两个点相遇时, 权重较小的点消失。
求出每个点消失时的坐标。
$1 \leq n \leq 5 \times 10^5,~ 1 \leq x_i \leq 10^9,~ a$ 是长度为 $n$ 的排列。
找到距离最小的一对 $(i,j)$ ,令一方不动,另一方向其靠近。
容易发现,此时在 $(i,j)$ 碰撞前,其他点不会发生碰撞。
记经过 $T$ 秒发生碰撞,则一个 $T$ 秒前一个向右运动的点 $i$ 的新坐标为 $x_i + T$ 。
由此我们得到了一个思路,于是尝试继续寻找它的性质。
注意到,当碰撞发生后,至多只有 $2$ 个点会改变运动方向。
这意味着,如果我们能够快速找到相距最小的那对 $(i,j)$ ,就可以 $\mathcal{O}(1)$ 更新所有关联的点的状态
于是考虑用 set 来维护这个最小间距。但是这会出现一个问题,每次点对的间距只能在加入时计算。
当时间增加时,我们无法更新原来的间距。那么怎么办呢?
可以发现,无法直接比较点对间距的原因是它们加入 set 时计算间距的时间是不同的。
于是我们定义等效间距 $T+d$ ,其中 $T$ 为当前时刻,初始为 $0$ 。
当一次碰撞发生时,我们只更新与消失的点 $i$ 相邻的点(显然只有这两个点可以凑成新点对)
记 lasti 表示上次更新 $i$ 坐标的时刻,上次更新 $i$ 后的坐标是 $x_i$ ,当前时刻为 $T$ 。
则 $i$ 当前的坐标为 $x_i + (T -$ lasti$) \times$ toi ,其中 toi 表示方向(左为 $-1$ ,右为 $1$ ,不动时为 $0$ )
两次更新之间 $i$ 的运动方向不可能改变,这也是可以用等效间距维护最小间距的原因。
时间复杂度 $\mathcal{O}(n \log n)$
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)(5e5 + 15))
int n,T,x[N],a[N],d[N],to[N],ans[N],last[N];
set<int> ok; set< tuple<int,int,int> > st;
int get_to(int pre, int x, int suf)
{
if(a[pre] > a[suf]) {
if(a[pre] > a[x]) return -1;
}else { if(a[suf] > a[x]) return 1; }
return 0;
}
int get_p(int i) { return x[i] + (T - last[i]) * to[i]; }
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
// freopen("check.in","r",stdin);
// freopen("check.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i++) cin >> x[i];
for(int i = 1; i <= n; i++) cin >> a[i], ok.insert(i);
for(int i = 1; i <= n; i++) to[i] = get_to(i - 1, i, i + 1);
// for(int i = 1; i <= n; i++) cout << to[i] << " \n"[i == n];
x[0] = -INF, x[n + 1] = INF; ok.insert(0), ok.insert(n + 1);
for(int i = 1; i <= n; i++) if(to[i] == -1) d[i] = d[i - 1] - x[i - 1] + x[i];
for(int i = n; i; i--) if(to[i] == 1) d[i] = d[i + 1] - x[i] + x[i + 1];
for(int i = 1; i <= n; i++) if(to[i] != 0) {
if(to[i + to[i]] != to[i]) st.insert({d[i], a[i + to[i]], i});
}
for(int cnt = 1; cnt < n; cnt++)
{
auto begin = st.begin(); int i = get<2>(*begin);
T = last[i] + d[i]; st.erase(st.begin());
int j = (to[i] == 1) ? *(++ok.find(i)) : *(--ok.find(i));
ans[i] = x[j];
int k = (to[i] == -1) ? *(++ok.find(i)) : *(--ok.find(i));
ok.erase(i);
if(to[i] == to[k])
{
x[k] = get_p(k); last[k] = T;
st.insert({T + (d[k] = abs(x[k] - x[j])), a[j], k});
}else
{
if(k == 0 || k == n + 1) continue;
int t = get_to(*(--ok.find(k)), k, *(++ok.find(k)));
if(t != to[k])
{
if(!to[k])
{
int h = (t == -1) ? *(++ok.find(k)) : *(--ok.find(k));
if(st.find({d[h] + last[h], a[k], h}) != st.end())
st.erase({d[h] + last[h], a[k], h});
}
x[k] = get_p(k);
int p = (to[k] == 1) ? *(++ok.find(k)) : *(--ok.find(k));
if(st.find({d[k] + last[k], a[p], k}) != st.end())
st.erase({d[k] + last[k], a[p], k});
to[k] = t;
st.insert({T + (d[k] = abs(x[k] - x[j])), a[j], k}); last[k] = T;
}
}
}
for(int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
return 0;
}
参考文献: