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\)。定义
取固定的 \(x\),将 \((a_i,i)\) 排序并正反扫两次。记录最近属于块 \(x\) 的元素 \(j\),扫到 \(i\) 时用 \(|a_i-a_j|\) 更新 \(f(i,x)\) 即可。
固定 \(x\) 和 \(b_i\),对 \(f\) 的 \(i\) 维度做块内前后缀 \(\min\)
枚举整块 \(x\),左散块 \(l\) 的贡献就是 \(s(l,x)\),右侧同理。

整块 \((x,y)\) 的贡献
令整块区间 \(x\sim y\) 的贡献
初值 \(h(x,x)=g(x,x)\)。\(h\) 可以做二维前缀 \(\min\) 递推
查询 \([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';
}

浙公网安备 33010602011771号