很经典但是很好的题目。/qiang
标签:线段树。
- 数轴上有一些关键点,不同的关键点可能在同一坐标。关键点的坐标均为整数。
- 支持两种操作:
- 删去 / 添加一些关键点。
- 取一个点。使得它与 \([l, r]\) 范围内所有关键点的距离最小。求最小距离。
- \(\text{关键点的坐标数}\le 3\times 10^5\),\(\text{所有坐标的绝对值}\le 10^9\)。
首先每个坐标上有多少个关键点是很好维护的。如果已经取了个点,如何计算答案?一个经典做法是把绝对值拆开。假设取的点坐标是 \(x\),左边有 \(a\) 个关键点,它们的坐标和为 \(S_a\);右边有 \(b\) 个关键点,坐标和为 \(S_b\)。则答案为 \((ax-S_a) + (S_b - bx)\)。用线段树维护 区间内关键点的个数 与 坐标和 即可。
问题来到了如何找点 \(x\)。有一种比较感性的思考方式:随便取一个 \(x\),我们将 \(x\) 向左移 \(\Delta x\) 且不越过关键点,那么距离增加了 \(b\Delta x - a\Delta x = (b - a)\Delta x\)。如果 \(a > b\) 即左边的点较多时,\((b - a)\Delta x < 0\),则距离减少了。也就是说,当左边的点多时,向左移是优的;同理当右边的点较多时,向右移是优的。那么最终 \(x\) 会停在区间内坐标从小到大第 \(\frac{\text{点的个数}}{2}\) 个点的位置。求这个坐标可以线段树上二分。
用权值线段树维护即可。时间复杂度 \(O(n\log n)\)。我写的是动态开点线段树,获得了线段树写法的最优解。code:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 300010, eps = 1e9, V = 1e9;
int cnt, ro;
struct Seg {
int ls, rs;
ll c, v;
} T[N * 4 * 32];
int n, m; ll a[N], b[N];
#define ls(k) (T[k].ls)
#define rs(k) (T[k].rs)
inline int read() {
int x = 0, f = 1;
char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
return x * f;
}
inline void write(ll x) {
if (x > 9) write(x / 10);
putchar(x % 10 + 48);
}
void pushup(int k) {
T[k].c = T[ls(k)].c + T[rs(k)].c;
T[k].v = T[ls(k)].v + T[rs(k)].v;
}
void update(ll l, ll r, int &k, ll x, ll c) {
if(!k) k = ++cnt;
if(l == r) {
T[k].c += c;
T[k].v = x * T[k].c;
return;
}
ll mid = (l + r) / 2;
if(x <= mid) update(l, mid, ls(k), x, c);
else update(mid + 1, r, rs(k), x, c);
pushup(k);
}
ll getc(ll l, ll r, int k, ll L, ll R) {
if(!k) return 0;
if(L <= l && r <= R) return T[k].c;
ll mid = (l + r) / 2, res = 0;
if(L <= mid) res += getc(l, mid, ls(k), L, R);
if(mid < R) res += getc(mid + 1, r, rs(k), L, R);
return res;
}
ll getv(ll l, ll r, int k, ll L, ll R) {
if(!k) return 0;
if(L <= l && r <= R) return T[k].v;
ll mid = (l + r) / 2, res = 0;
if(L <= mid) res += getv(l, mid, ls(k), L, R);
if(mid < R) res += getv(mid + 1, r, rs(k), L, R);
return res;
}
ll find(ll l, ll r, int k, ll c) {
if(l == r) return l;
int mid = (l + r) / 2;
if(c <= T[ls(k)].c) return find(l, mid, ls(k), c);
else return find(mid + 1, r, rs(k), c - T[ls(k)].c);
}
int main() {
// ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
n = read(), m = read();
for (int i = 1; i <= n; i++) a[i] = read() + eps;
for (int i = 1; i <= n; i++) {
b[i] = read();
update(0, 2e9, ro, a[i], b[i]);
}
while(m--) {
int op = read();
if(op == 2) {
ll x = read(), y = read() + eps, z = read();
update(0, 2e9, ro, a[x], -b[x]);
a[x] = y, b[x] = z;
update(0, 2e9, ro, a[x], b[x]);
} else {
ll l = read() + eps, r = read() + eps;
ll mid = getc(0, 2e9, ro, 0, l - 1) + (getc(0, 2e9, ro, l, r) + 1) / 2;
mid = find(0, 2e9, ro, mid);
write(mid * getc(0, 2e9, ro, l, mid - 1) - getv(0, 2e9, ro, l, mid - 1) + getv(0, 2e9, ro, mid + 1, r) - mid * getc(0, 2e9, ro, mid + 1, r));
puts("");
}
}
return 0;
}
如有错误烦请您指出。
标签:P6309,int,题解,ll,ls,坐标,线段,关键点 From: https://www.cnblogs.com/Running-a-way/p/18455220