P5926 [JSOI2009] 面试的考验

lxl 上课讲的题,来写个题解。

双倍经验:CF765F

三倍经验:CF1793F

题目传送门

  • 给出序列 a1an,有 q 次询问,每次询问给出 l,r,求两个数 i,j(li,jr) 满足 aiaj|aiaj| 最小。输出这个最小值。

  • n,q105。设值域为 V|V|109

不妨令 i<j。称 |aiaj| 为点对 (i,j) 的权值。

一个朴素的想法是,把所有的点对 (i,j) 的值计算出来,然后就是做满足 lijr 的二维偏序。

但是真的所有 (i,j) 都有贡献吗?考虑这样一种情况:i<j1<j2ai<aj1aj2,若 [l,r] 包含了 (i,j2) 这个点对,就一定包含了 (i,j1) 这个点对,而且 |aiaj1||aiaj2|,所以 (i,j2) 这个点对没有贡献。这时我们称 (i,j1) 支配了 (i,j2)

简单理解一下,一个点对能够支配另一个点对,当且仅当这个点对的限制更宽松,且权值不超过另一个点对

考虑把所有有贡献的点对 (i,j) 找出来。先固定左点 i 按顺序一个个从左往右找。分为两种情况:

  • ai<aj,此时权值 |aiaj|=ajai

    设当前找到 (i,p) 是有贡献的点对,考虑怎样的 (i,q)(满足 p<q)仍然有贡献。首先 ai<aq<ap,不然 (i,p) 会支配 (i,q)

    其次,aqai<apaq,不然 (p,q) 会支配 (i,q)。此时我们将不等式变形,会发现 aqai<apai2,即权值至少减半。因此以 i 为左点的有贡献点对不会超过 O(log|V|)

    因为找到 O(log|V|) 个点对后,权值已经变成 0,由于点对的权值是绝对值,显然不可能再找到一个点对,使得它的值小于 0

    此时,ai<aq<ai+ap2

  • ai>aj 的情况类似分析即可。

这么一来,需要保留的点对就只有 O(nlog|V|) 个。更严谨地来讲,与其说上面的过程是在找出所有有贡献的点对,不如说是去除一些点对,使得剩下的点对包含最优解,且数量可以接受。

为了配合上述过程找出有贡献点对,我们需要支持这个操作:

查询一个最小的位置 x,使得 x>yax[u,v]

没懂这一步大佬们是如何不可持久化做的,只能来介绍一下我的憨憨做法。建立一棵可持久化权值线段树,版本 i 的线段树维护的是每种权值在 [i,n] 中出现的最左位置,节点内维护区间最小值。若某种权值未出现,则设为无穷大。查询的时候,在 y+1 版本查询 [u,v] 的最小值。

设为未出现的权值设为无穷大的意义就体现出来了,我要查询的是出现过的数的最小位置,没出现过的数因为权值无穷大,会在取最小值时被舍去。若查询结果为最小值,则不存在这样的数,说明不能找到更多的点对了。

把所有有贡献的点对保留后,离线询问,将第一维降序排序(修改在前),用权值树状数组维护第二维的限制。

时间复杂度为 O(nlog2|V|),空间复杂度为 O(nlog|V|)

提交记录

#include <bits/stdc++.h>
#define lb(x) ((x) & (-(x)))
using namespace std; const int N = 3e5 + 2, inf = 0x3f3f3f3f, L = 1, R = 1e9; 
int n, a[N], m, cnt, ans[N << 2];
template<class T> void read(T &x) {
    x = 0; T f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -1;
    for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + c - 48; x *= f;
}
template<class T> void write(T x) {
    if (x > 9) write(x / 10); putchar(x % 10 + 48);
}
template<class T> void print(T x, char ed = '\n') {
    if (x < 0) putchar('-'), x = -x; write(x), putchar(ed);
}
struct ChairmanTree {
    int ls[N * 20], rs[N * 20], mn[N * 20], rt[N], cnt;
    void init() {
        memset(ls, 0, sizeof ls); memset(mn, 0x3f, sizeof mn);
        memset(rs, 0, sizeof rs); memset(rt, cnt = 0, sizeof rt);
    }
    void modify(int &x, int y, int l, int r, int k, int v) {
        x = ++cnt; mn[x] = min(mn[y], v); 
        if (l == r) return; int mid = (l + r) >> 1;
        if (k <= mid) rs[x] = rs[y], modify(ls[x], ls[y], l, mid, k, v);
        else ls[x] = ls[y], modify(rs[x], rs[y], mid + 1, r, k, v);
    }
    int query(int x, int l, int r, int ql, int qr) {
        if (!x || ql > qr) return inf; 
        if (ql <= l && r <= qr) return mn[x]; int mid = (l + r) >> 1, ret = inf;
        if (ql <= mid) ret = query(ls[x], l, mid, ql, qr);
        if (qr > mid) ret = min(ret, query(rs[x], mid + 1, r, ql, qr)); return ret;
    }
} T;
struct BinaryIndexedTree {
    int mn[N]; void init() { memset(mn, 0x3f, sizeof mn); }
    void modify(int x, int v) { 
        while (x <= n) mn[x] = min(mn[x], v), x += lb(x); 
    }
    int query(int x) {
        int ret = inf; while (x) ret = min(ret, mn[x]), x -= lb(x); return ret;
    }
} B;
struct node { int x, y, id; } p[N * 25]; 
signed main() {
    read(n); read(m); for (int i = 1; i <= n; ++i) read(a[i]); T.init();
    T.modify(T.rt[n], T.rt[n + 1], L, R, a[n], n); B.init();
    for (int i = n - 1, v; i >= 1; --i) {
        int pos = T.query(T.rt[i + 1], L, R, a[i] + 1, R);
        while (pos != inf) {
            v = ((a[i] + a[pos]) >> 1) - (((a[i] + a[pos]) & 1) ^ 1);
            p[++cnt] = {i, pos, 0}; 
            pos = T.query(T.rt[pos + 1], L, R, a[i] + 1, v);
        }
        T.modify(T.rt[i], T.rt[i + 1], L, R, a[i], i);
    }
    T.init(); T.modify(T.rt[n], T.rt[n + 1], L, R, a[n], n); 
    for (int i = n - 1, v; i >= 1; --i) {
        int pos = T.query(T.rt[i + 1], L, R, L, a[i] - 1);
        while (pos != inf) {
            p[++cnt] = {i, pos, 0}; v = ((a[i] + a[pos]) >> 1) + 1;
            pos = T.query(T.rt[pos + 1], L, R, v, a[i] - 1);
        }
        T.modify(T.rt[i], T.rt[i + 1], L, R, a[i], i);
    }
    for (int i = 1, l, r; i <= m; ++i) { read(l), read(r); p[++cnt] = {l, r, i}; }
    stable_sort(p + 1, p + cnt + 1, [&](node u, node v) { 
        return u.x != v.x ? u.x > v.x : !u.id; 
    });
    for (int i = 1; i <= cnt; ++i)
        if (p[i].id) ans[p[i].id] = B.query(p[i].y);
        else B.modify(p[i].y, abs(a[p[i].x] - a[p[i].y]));
    for (int i = 1; i <= m; ++i) print(ans[i]); return 0;
}
posted @   lzyqwq  阅读(42)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示