【CF1635F】 Closest Pair 题解

· · 个人记录

CF 传送门:CF1635F

找性质规律 + 单调栈 + 线段树

Solution

1

考场上想出了把那些点对扔进单调队列然后乱搞的做法。发现对于一个询问区间,如果想要直接都扔进单调队列然后从中找到带权距离最小值复杂度仍然很高。

换句话说,我们无法直接得到区间最优解。那不妨来试着求解单点最优解,然后在询问区间内合并答案得到最优。

2

一般这种情况,需要我们讨论 3 个或以上个节点寻找性质规律。先考虑全局。

不妨假设有三个节点 a,\ b,\ c,满足 x_a<x_b<x_cw_b<w_c。不考虑 w_a 的数量关系是因为若这个性质需要 w 也满足单调(x 本身满足单调),那么限制条件过多,不好做。

我们由此可以发现,三点不论怎样组合,d(a,c) 一定不是最优的——d(a,b),\ d(b,c) 显然都比他优。又因为我们要寻找单点最优解,故对于节点 c 而言,节点 b 之前的节点对它都没有意义(换句话说,对全局不会有贡献)。 这个性质对 c<b<a 的情况同样成立。

综述,对于每个节点 i,我们都可以找到两个相应节点 l_i,\ r_i,满足 l_i<i,\ W_{l_i}<W_i,\ r_i>i,\ W_{r_i}<W_i。而有且仅有这 2n 个点对可以对全局结果产生贡献(因为它们相对其他点对都更优)。

3

回过头看区间上如何处理。

自然会有疑问:对于一个单点 i,如果其 l_ir_i 都在区间询问范围之外,即我们考虑不到,但是 i 本身却可以和区间内其他节点构成本区间的最优解呢?

为了解决这个问题,不妨构造出一个和问题所述一样的情景:对于询问区间 [L,R],有带权距离最小的点对 (a,b)|w_a<w_b,满足 a>l_b

按照上面得到的性质,如果 w_a<w_ba<b,那么显然 l_b=a,与假设矛盾。而 w_b>w_a 的情况同理。

综上,我们得到结论:区间内的带权距离最小的点对一定属于那 2n 个点对之中。

4

~~直觉~~经验告诉我们查找区间最优点对可以使用线段树实现。具体地,将每个 $d(l_i,i)$ 存在点 $l_i$ 上,$r_i$ 同理,再将询问离线下来,按 $R$ 升序依次查询处理即可。 ## Code ```cpp #include<bits/stdc++.h> using namespace std; #define int long long #define per(i, a, b) for(register int i = a; i >= b; --i) #define rep(i, a, b) for(register int i = a; i <= b; ++i) const int maxn = 3e5 + 5; int n, q; int x[maxn], w[maxn], st[maxn], top; struct node{int l, id;}; vector<node> que[maxn]; int ans[maxn]; vector<int> p[maxn]; int fr[maxn], bc[maxn], l, r; struct segment_tree{ #define ls(x) (x<<1) #define rs(x) (x<<1|1) int mn[maxn << 2]; inline void updt(int i, int l, int r, int pos, int k){ if(l == r){ mn[i] = min(mn[i], k); return; } int mid = l + r >> 1; if(pos <= mid) updt(ls(i), l, mid, pos, k); else updt(rs(i), mid + 1, r, pos, k); mn[i] = min(mn[ls(i)], mn[rs(i)]); } inline int qry(int i, int l, int r, int L, int R){ if(l >= L and r <= R) return mn[i]; int mid = l + r >> 1, res = 5e18; if(L <= mid) res = min(res, qry(ls(i), l, mid, L, R)); if(R > mid) res = min(res, qry(rs(i), mid + 1, r, L, R)); return res; } }T; signed main(){ scanf("%lld%lld", &n, &q); rep(i, 1, n << 2) T.mn[i] = 5e18; rep(i, 1, n) scanf("%lld%lld", &x[i], &w[i]); rep(i, 1, n){ while(top and w[st[top]] > w[i]) top -= 1; fr[i] = st[top], st[++top] = i; } top = 0; per(i, n, 1){ while(top and w[st[top]] > w[i]) top -= 1; bc[i] = st[top], st[++top] = i; } rep(i, 1, n){ if(fr[i]) p[i].push_back(fr[i]); if(bc[i]) p[bc[i]].push_back(i); } rep(i, 1, q) scanf("%lld%lld", &l, &r), ans[i] = 5e18, que[r].push_back({l, i}); rep(i, 1, n){ for(auto j : p[i]) T.updt(1, 1, n, j, 1ll * abs(x[i] - x[j]) * (w[i] + w[j])); for(auto j : que[i]) ans[j.id] = T.qry(1, 1, n, j.l, i); } rep(i, 1, q) printf("%lld\n", ans[i]); return 0; } ```