CF765F Souvenirs

CF765F Souvenirs

给定长为 \(n\) 的序列 \(a_i\)。进行 \(q\) 次询问,给定 \(l,r\),查询

\[\min_{i,j \in [l,r], i\ne j} |a_i-a_j| \]

\(n \le 10^5\),\(q \le 3 \times 10^5\),3 秒,500 MB。

解法一

钦定 \(i<j\)。转 \(l-r\) 二维平面分析,将坐标 \((i,j)\) 的权值定义为 \(|a_i-a_j|\),则查询是 2-side 矩形求 \(\min\),我们需要减少点对数,这属于第二类支配对。

不妨设 \(a_j \ge a_i\),对于 \(a_j<a_i\) 的情形同理。

取固定的 \(i\) 向右扫描,则 \(j >i \land a_j \ge a_i\) 的最小的 \(j\) 形成的 \((i,j)\) 必然是支配对。考虑下一个位置 \(k\) 满足 \(i<j<k\) 且 \(a_i \le a_j\):

  • 若 \(a_k \ge a_j\),则 \(|a_i-a_k| \ge |a_i-a_j|\),点对 \((i,j)\) 支配 \((i,k)\)。
  • 若 \(a_k<a_j\),列出 \((i,k)\) 不被 \((j,k)\) 支配的条件为 \(|a_i-a_k| < |a_j-a_k|\),去掉绝对值得到 \(a_k-a_i < a_j-a_k\),移项得 \(a_k-a_i<\dfrac{a_j-a_i}{2}\)。

根据推导,每向右寻找一次支配对 \((i,k)\),差值至少折半,故 \(k\) 只会有 \(O(\log V)\) 个,支配对的总数为 \(O(n \log V)\)。

我们倒序扫描 \(i=n \to 1\),使用权值线段树维护值域区间内的最小下标。对当前位置 \(i\),设值域上界 \(R=+\infty\),我们在线段树 \([a_i,R]\) 上查询最小的 \(j\),则 \((i,j)\) 为支配对。令 \(R \gets a_i +\lfloor \frac{a_j-a_i-1}{2}\rfloor\) 重复上述过程,直到 \(j\) 不存在结束这个过程,内层循环至多 \(O(\log V)\) 轮。

求出所有支配对后,问题转为静态 2-side 矩形数点,扫描线配合树状数组即可。

复杂度 \(O(n \log n \log V+q\log n)\)。

const int N = 1e5 + 5, M = 3e5 + 5, inf = 2e9;
int n, q, m, a[N], d[N], ans[M];
int id(int x) {return lower_bound(d + 1, d + m + 1, x) - d;}
vector<pair<int, int>> P[N];  // (y, val)
vector<pair<int, int>> Q[N];  // (y, id)

struct segtree {
    #define ls (rt << 1)
    #define rs (rt << 1 | 1)
    int val[4*N];
    void pushup(int rt) {val[rt] = min(val[ls], val[rs]);}
    void build(int l = 1, int r = m, int rt = 1) {
        val[rt] = inf; 
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(l, mid, ls), build(mid + 1, r, rs);
    }
    void update(int x, int c, int l = 1, int r = m, int rt = 1) {
        if (l == r) return chkmin(val[rt], c), void();
        int mid = (l + r) >> 1;
        if (x <= mid) update(x, c, l, mid, ls);
        else update(x, c, mid + 1, r, rs);
        pushup(rt);
    }
    int ask(int tl, int tr, int l = 1, int r = m, int rt = 1) {
        if (tl > tr) return inf;
        if (tl <= l && r <= tr) return val[rt];
        int mid = (l + r) >> 1;
        if (tl <= mid && mid < tr) return min(ask(tl, tr, l, mid, ls), ask(tl, tr, mid + 1, r, rs));
        return tl <= mid ? ask(tl, tr, l, mid, ls) : ask(tl, tr, mid + 1, r, rs);
    }
} T;

struct BIT {
    int a[N];
    void clear() {fill(a + 1, a + n + 1, inf);}
    void add(int x, int c) {
        for (; x <= n; x += lowbit(x)) chkmin(a[x], c);
    }
    int ask(int x) {
        int res = inf;
        for (; x >= 1; x -= lowbit(x)) chkmin(res, a[x]);
        return res;
    }
} B;

void _main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i], d[++m] = a[i];
    d[++m] = -inf, d[++m] = inf; 
    sort(d + 1, d + m + 1), m = unique(d + 1, d + m + 1) - d - 1;
    T.build(), B.clear();
    for (int i = n; i >= 1; i--) {
        int x = id(a[i]);
        for (int hi = inf, j; ; ) {
            j = T.ask(x, upper_bound(d + 1, d + m + 1, hi) - d - 1);
            if (j == inf) break;
            P[i].pb(j, a[j] - a[i]);
            if (a[i] == a[j]) break;
            hi = a[i] + (a[j] - a[i] - 1) / 2;
        }
        for (int lo = -inf, j; ; ) {
            j = T.ask(id(lo), x - 1);
            if (j == inf) break;
            P[i].pb(j, a[i] - a[j]);
            if (a[i] == a[j]) break;
            lo = a[i] - (a[i] - a[j] - 1) / 2;
        }
        T.update(x, i);
    }
    cin >> q;
    for (int i = 1, l, r; i <= q; i++) 
        cin >> l >> r, Q[l].pb(r, i);
    for (int x = n; x >= 1; x--) {
        for (auto [y, v] : P[x]) B.add(y, v);
        for (auto [y, id] : Q[x]) ans[id] = B.ask(y);
    }
    for (int i = 1; i <= q; i++) cout << ans[i] << '\n';
}

解法二

对序列分块,设第 \(x\) 块覆盖 \([L_x,R_x]\),位置 \(i\) 所属块标号为 \(b_i\)。定义

\[f(i,x)=\min_{j \in [L_x,R_x] \land i\ne j} |a_i-a_j| \]

取固定的 \(x\),将 \((a_i,i)\) 排序并正反扫两次。记录最近属于块 \(x\) 的元素 \(j\),扫到 \(i\) 时用 \(|a_i-a_j|\) 更新 \(f(i,x)\) 即可。

固定 \(x\) 和 \(b_i\),对 \(f\) 的 \(i\) 维度做块内前后缀 \(\min\)

\[\begin{aligned} p(i,x)&=\min_{k\in [L_{b_i},i]} f_{k,x}\\ s(i,x)&=\min_{k \in [i,R_{b_i}]} f_{k,x} \end{aligned} \]

枚举整块 \(x\),左散块 \(l\) 的贡献就是 \(s(l,x)\),右侧同理。

image.png

整块 \((x,y)\) 的贡献

\[\begin{aligned} g(x,y)&=\min_{i \in [L_x,R_x] \land j\in [L_y,R_y] \land i \ne j} |a_i-a_j|\\ &=p(R_x,y) \end{aligned} \]

令整块区间 \(x\sim y\) 的贡献

\[h(x,y)=\min_{L_x \le i < j \le R_y} |a_i-a_j| \]

初值 \(h(x,x)=g(x,x)\)。\(h\) 可以做二维前缀 \(\min\) 递推

\[h(x,y)=\min\left(h(x+1,y),h(x,y-1),g(x,y)\right) \]

查询 \([l,r]\) 时,取 \(x=b_l,y=b_r\),贡献分三类处理:

  • 数对 \((i,j)\) 落在整块 \(x+1\sim y-1\) 中,查表 \(h(x+1,y-1)\)。
  • 数对 \((i,j)\) 在散块 \([l,R_x] \cup [L_y,r]\) 中。对块内预处理 \((a_i,i)\) 的有序数组,扫出子序列归并后更新答案。
  • 数对 \((i,j)\) 中 \(i\) 落在散块,\(j\) 落在整块。枚举整块 \(z \in (x,y)\),用 \(s(l,z)\) 和 \(p(r,z)\) 更新答案。

块长取 \(\sqrt{n}\) 得到 \(O(n\sqrt{n}+q\sqrt{n})\) 复杂度,支持在线查询。

一个重要的常数优化是,由于 \(h(x,y)\) 是答案下界,故候选值 \(=h(x,y)\) 时直接返回即可。卡常后可以通过。

const int N = 1e5 + 5, B = 320, inf = 0x3f3f3f3f;
using node = pair<int, int>;
int n, q; node a[N];
int m, L[N], R[N], bel[N];
vector<node> seq[B];
int f[B][N], p[B][N], s[B][N], h[B][B];

int ask(int l, int r) {
    int x = bel[l], y = bel[r], res = inf;
    vector<int> vec;
    if (x == y) {
        for (auto [v, i] : seq[x])
            if (l <= i && i <= r) vec.pb(v);
        for (int i = 1; i < (int)vec.size(); i++) 
            chkmin(res, vec[i] - vec[i-1]);
        return res;
    }
    if (x+1 <= y-1) res = h[x+1][y-1];
    if (res == h[x][y]) return res;
    for (auto [v, i] : seq[x]) if (l <= i) vec.pb(v);
    int u = vec.size();
    for (auto [v, i] : seq[y]) if (i <= r) vec.pb(v);
    inplace_merge(vec.begin(), vec.begin() + u, vec.end());
    for (int i = 1; i < (int)vec.size(); i++) {
        chkmin(res, vec[i] - vec[i-1]);
        if (res == h[x][y]) return res;
    }
    for (int z = x+1; z <= y-1; z++) {
        chkmin(res, s[z][l]), chkmin(res, p[z][r]);
        if (res == h[x][y]) return res;
    }
    return res;
}

void _main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].fi, a[i].se = i;
    for (int i = 1; i <= n; i += B) {
        ++m, L[m] = i, R[m] = min(n, i+B-1);
        fill(bel + L[m], bel + R[m] + 1, m);
        seq[m] = vector<node>(a + L[m], a + R[m] + 1);
        sort(all(seq[m]));
    }
    memset(f, 0x3f, sizeof(f));
    sort(a + 1, a + n + 1);
    for (int x = 1; x <= m; x++) {
        for (int i = 1, j = -1; i <= n; i++) {
            int p = a[i].se;
            if (j != -1) chkmin(f[x][p], a[i].fi - a[j].fi);
            if (bel[p] == x) j = i;
        }
        for (int i = n, j = -1; i >= 1; i--) {
            int p = a[i].se;
            if (j != -1) chkmin(f[x][p], a[j].fi - a[i].fi);
            if (bel[p] == x) j = i;
        }
    }
    for (int b = 1; b <= m; b++)
        for (int x = 1; x <= m; x++) {
            p[x][L[b]] = f[x][L[b]], s[x][R[b]] = f[x][R[b]];
            for (int i = L[b]+1; i <= R[b]; i++) p[x][i] = min(p[x][i-1], f[x][i]);
            for (int i = R[b]-1; i >= L[b]; i--) s[x][i] = min(s[x][i+1], f[x][i]);
        }
    memset(h, 0x3f, sizeof(h));
    for (int x = 1; x <= m; x++) h[x][x] = p[x][R[x]];
    for (int x = m; x >= 1; x--)
        for (int y = x+1; y <= m; y++)
            h[x][y] = min({h[x+1][y], h[x][y-1], p[y][R[x]]});
    cin >> q;
    for (int l, r; q--; ) 
        cin >> l >> r, cout << ask(l, r) << '\n';
} 
posted @ 2026-11-11 20:52  stripe_python  阅读(9)  评论(0)    收藏  举报