CF765F Souvenirs

知识点: 线段树

原题面 Luogu

这种写法好傻逼啊,调吐了,边界尼美舒利。
感谢 神仙 zzy提交记录 救我一命!

简述

给定一长度为 \(n\) 的序列 \(a\)\(m\) 次询问。
每次询问给定参数 \(l,r\),求 \(\min\limits_{i,j\in[l,r](l\not= r)}|a_i-a_j|\)
\(1\le n\le 10^5\)\(1\le m\le 3\times 10^5\)\(0\le a_i\le 10^9\)
3S,512MB。

分析

不强制在线,考虑离线询问,按右端点排序。
升序枚举右端点,考虑用线段树维护 右端点为当前枚举到的端点的 不同左端点的答案。
枚举到某询问的右端点 则回答对应询问。
每次仅需考虑 \(r-1\rightarrow r\) 对答案的影响,即必选择 \(a_r\) 的数对。

分类讨论,先考虑 \(a_r\)\(a_l>a_r(1<l<r)\) 组成的数对,其贡献为 \(a_r - a_l\)
先找到最大的 \(x\),满足 \(a_x>a_r\),显然对于 \(1<l\le x\),新增了一个候选答案为 \(a_r-a_l\),线段树区间取 \(\min\) 即可。

但这样还不够,可能 \(\exist y<x, a_y>a_r\),且 \(a_r-a_y<a_r-a_x\),可能对答案作出贡献。
又发现 \(y\) 想要对答案做出贡献,还需满足 \(a_r-a_y < a_y-a_x\),否则数对 \((a_x,a_y)\) 更优。
\(a_y\) 的取值范围为:\(a_r<a_y<\dfrac{a_r+a_x}{2}\)
已知 \(y\) 的取值范围和位置范围,考虑使用主席树维护 权值 区间内 最右侧的位置。
注意查询的边界取值,在爆零小技巧中会给出。
找到 \(y\) 后,更新 \(1\le l\le y\) 的答案。

同理,还可能 \(\exist y<x, a_y>a_r\),且 \(a_r<a_z<\dfrac{a_r+a_y}{2}\)
不断找下去并更新答案。
过程中每次主席树查询的值域 都会减半,故查询次数不多于 \(\log r\) 次。

再考虑 \(a_r\)\(a_l<a_r(1<l<r)\) 组成的数对,方法同上。

复杂度 \(O(n\log^2 n)\) 级别。

注意一个奇怪的边界问题。对于 \(x\in \Z\)

\(x\le \dfrac{a + b}{2}\iff x\le \left\lfloor\dfrac{a+b}{2}\right\rfloor\)

\(x> \dfrac{a+b}{2}\iff x\ge \left\lfloor\dfrac{a+b}{2}\right\rfloor+1\)

代码

//知识点:线段树,主席树
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <ctype.h>
#include <cstring>
#define ll long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 3e5 + 10;
const int kInf = 1e9 + 5;
//=============================================================
struct Que {
  int l, r, id;
} q[kMaxm];
int n, m, a[kMaxn], ans[kMaxm];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
bool Compare(Que fir, Que sec) {
  if (fir.r != sec.r) return fir.r < sec.r;
  return fir.l < sec.l;
}
//查找最大下标,主席树,维护 **权值** 区间内 最右侧的位置 
struct ScientificConceptOfDevelopmentTree {
  #define ls (lson[now_])
  #define rs (rson[now_])
  #define mid ((L_+R_)>>1) 
  int node_num, root[kMaxn];
  int lson[kMaxn << 5], rson[kMaxn << 5], maxpos[kMaxn << 5];
  void Insert(int pre_, int &now_, int L_, int R_, int val_, int pos_) {
    now_ = ++ node_num;
    maxpos[now_] = pos_, ls = lson[pre_], rs = rson[pre_];
    if (L_ >= R_) return ;
    if (val_ > mid) Insert(rson[pre_], rs, mid + 1, R_, val_, pos_);
    else Insert(lson[pre_], ls, L_, mid, val_, pos_);
  }
  int Query(int now_, int L_, int R_, int ql_, int qr_) {
    if (L_ > R_) return 0; // 
    if (ql_ <= L_ && R_ <= qr_) return maxpos[now_];
    int ret = 0;
    if (ql_ <= mid) GetMax(ret, Query(ls, L_, mid, ql_, qr_));
    if (qr_ > mid) GetMax(ret, Query(rs, mid + 1, R_, ql_, qr_));
    return ret;
  }
} hjt;
//区间取最小值,单点查询
struct SegmentTree {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  int minnum[kMaxn << 2], tag[kMaxn << 2]; 
  void Build(int now_, int L_, int R_) {
    minnum[now_] = kInf, tag[now_] = kInf;
    if (L_ == R_) return ;
    Build(ls, L_, mid), Build(rs, mid + 1, R_);
  }
  void Pushup(int now_) {
    minnum[now_] = std :: min(minnum[ls], minnum[rs]);
  }
  void Pushdown(int now_) {
    if (tag[now_] == kInf) return ;
    GetMin(minnum[ls], tag[now_]), GetMin(tag[ls], tag[now_]);
    GetMin(minnum[rs], tag[now_]), GetMin(tag[rs], tag[now_]);
    tag[now_] = kInf;
  }
  void Modify(int now_, int L_, int R_, int l_, int r_, int val_) {
    if (L_ > R_) return ;
    if (l_ <= L_ && R_ <= r_) {
      GetMin(minnum[now_], val_);
      GetMin(tag[now_], val_);
      return ;
    }
    Pushdown(now_);
    if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_);
    Pushup(now_);
  }
  int Query(int now_, int L_, int R_, int pos_) {
    if (L_ >= R_) return minnum[now_];
    Pushdown(now_);
    if (pos_ <= mid) return Query(ls, L_, mid, pos_);
    return Query(rs, mid + 1, R_, pos_);
  }
} seg;
void Update(int r) {
  for (int pre = hjt.Query(hjt.root[r - 1], 0, kInf, a[r], kInf); pre; ) {
    seg.Modify(1, 1, n, 1, pre, a[pre] - a[r]);
    if (a[r] == a[pre]) break;
    pre = hjt.Query(hjt.root[pre - 1], 0, kInf, a[r], (a[r] + a[pre]) / 2);
  }
  for (int pre = hjt.Query(hjt.root[r - 1], 0, kInf, 0, a[r]); pre; ) {
    seg.Modify(1, 1, n, 1, pre, a[r] - a[pre]);
    if (a[r] == a[pre]) break;
    pre = hjt.Query(hjt.root[pre - 1], 0, kInf, (a[r] + a[pre]) / 2 + 1, a[r]); //
  } 
}
//=============================================================
int main() {
  n = read(); seg.Build(1, 1, n);
  for (int i = 1; i <= n; ++ i) a[i] = read();
  m = read();
  for (int i = 1; i <= m; ++ i) q[i] = (Que) {read(), read(), i};
  std :: sort (q + 1, q + m + 1, Compare);
  
  for (int r = 1, cntq = 1; r <= n; ++ r) {
    Update(r);
    for (; q[cntq].r <= r && cntq <= m; ++ cntq) ans[q[cntq].id] = seg.Query(1, 1, n, q[cntq].l);
    hjt.Insert(hjt.root[r - 1], hjt.root[r], 0, kInf, a[r], r);
  }
  for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]);
  return 0;
}
posted @ 2020-08-26 19:34  Luckyblock  阅读(204)  评论(2编辑  收藏  举报