[APIO2018]新家——线段树
题面
解析
看到这个题,第一反应就是离线按时间排序,枚举时间节点,用某种数据结构维护当前时间点的信息
然后我就不知道怎么维护了
结果是线段树?
当然维护信息的过程很巧妙,但需要想到二分长度,把询问转化为在区间$[x-mid, x+mid]$内是否存在$k$种商店才可能想到用线段树维护信息
线段树中每个叶子节点存与这个点具有相同类型的商店的前驱,但有可能多个商店在同一个点,因此就存所有前驱的最小值, 如果这个点没有了商店,那这个值就是$inf$,但也有可能多个相同类型的商店在同一个点,因此存下标小于这个点的前驱,由于有插入商店与删除商店的操作,因此对每个叶子节点开一个$multiset$维护信息,取出最小的元素作为当前叶子节点的值,再向上更新父亲节点即可。而为了在插入一个商店时可以快速确定它的前驱与后继进行修改,每一个商店类型也都需要开一个$multiset$维护这个类型的所有商店的下标。为了防止找不到前驱或后继的情况,在每个类型的商店的$multiset$中都插入$inf$与$-inf$,当然,相应地,就需要在线段树的$inf$处插入前驱$-inf$,总共插入$k$次
有了线段树维护信息,那么怎么判定当前二分的区间?
因为我们维护了前驱的下标,判定在区间$[x-mid, x+mid]$内是否存在k种商店, 变成判定在$(x+mid,inf]$中所有叶子节点的最小前驱的最小值是否大于等于$x-mid$,二分+线段树区间查询可以在$O(log^{2}N)$内完成一次查询。其实可以直接放在线段树上进行二分,每一次都是提取出一个后缀区间。设需要查询的下标是$x$,当前二分的节点是$[l, r]$,中点是$mid$,开一个变量$mn$存$(r, inf]$中所有前驱的最小值,一个临时变量$tmp$存$(mid, inf]$中所有前驱的最小值,即$tmp = min(mn, tr[rs].mn)$我们强制让$mid$在$x$的右边,即若$mid < x$,就走右儿子,不更新$mn$;若$mid - x < x - tmp$,也是走右儿子,也不更新$mn$;若$mid - x > x - tmp$,就走左二子,此时把$tmp$的值赋给$mn$。走到叶子节点时,$l - x$就是答案
最后一点细节,就是关于$inf$的取值问题。$inf$不能取太大,因为这和线段树的树高与大小有关,$inf$开大了,就会浪费空间与时间;而开小了在查询答案时会出错。考虑一种极限情况,当前查询的点在$1e8$, 而所有商店均在$1$,结合二分的判定条件,$inf$至少为$19999999$,那么开$2e8$足矣
代码:
#include<bits/stdc++.h> using namespace std; const int maxn = 300005, inf = 200000000; inline int read() { int ret, f=1; char c; while((c=getchar())&&(c<'0'||c>'9'))if(c=='-')f=-1; ret=c-'0'; while((c=getchar())&&(c>='0'&&c<='9'))ret=(ret<<3)+(ret<<1)+c-'0'; return ret*f; } int n, k, m, cnt, ans[maxn]; int tot; struct thi{ int pos, tim, num, typ; void init(int x, int y, int z, int u) { pos = x; tim = y; num = z; typ = u; } }t[maxn*3]; bool cmp(thi x, thi y) { return x.tim != y.tim? x.tim < y.tim: x.typ > y.typ; } multiset<int> st[maxn*60], s[maxn]; multiset<int> :: iterator pt, ptt; int root, ndnum; struct seg_tree{ int ls, rs, mn; }tr[maxn*60]; void update(int x) { tr[x].mn = min(tr[tr[x].ls].mn, tr[tr[x].rs].mn); } void Modify(int x, int L, int R, int p, int ad, int de) { if(L == R) { if(ad) st[x].insert(ad); if(de) st[x].erase(st[x].find(de)); tr[x].mn = (st[x].empty()? inf: *st[x].begin()); return ; } int mid = (L + R) >> 1; if(p <= mid) { if(!tr[x].ls) tr[x].ls = ++ ndnum; Modify(tr[x].ls, L, mid, p, ad, de); } else { if(!tr[x].rs) tr[x].rs = ++ ndnum; Modify(tr[x].rs, mid + 1, R, p, ad, de); } update(x); } int Query(int x) { int now = root, l = 0, r = inf, mid, mn = inf, tmp; while(l < r) { mid = (l + r) >> 1; tmp = min(mn, tr[tr[now].rs].mn); if(mid < x || mid - x < x - tmp) now = tr[now].rs, l = mid + 1; else mn = tmp, now = tr[now].ls, r = mid; } return l - x; } int main() { n = read(); k = read();m = read(); for(int i = 1; i <= n; ++i) { int x = read(), ty = read(), a = read(), b = read(); t[++tot].init(x, a, ty, 1); t[++tot].init(x, b, ty, -1); } for(int i = 1; i <= m; ++i) { int x = read(), y = read(); t[++tot].init(x, y, i, 0); } sort(t + 1, t + tot + 1, cmp); tr[0].mn = inf; root = ++ ndnum; for(int i = 1; i <= k; ++i) { s[i].insert(inf); s[i].insert(-inf); Modify(root, 0, inf, inf, -inf, 0); } for(int i = 1; i <= tot; ++i) { if(t[i].typ == 1) { if(s[t[i].num].size() == 2) ++ cnt; ptt = pt = s[t[i].num].upper_bound(t[i].pos); -- ptt; if(*ptt != t[i].pos) { Modify(root, 0, inf, *pt, t[i].pos, *ptt); Modify(root, 0, inf, t[i].pos, *ptt, 0); } s[t[i].num].insert(t[i].pos); } else if(t[i].typ == -1) { s[t[i].num].erase(s[t[i].num].find(t[i].pos)); ptt = pt = s[t[i].num].lower_bound(t[i].pos); if(*pt == t[i].pos) continue; -- ptt; Modify(root, 0, inf, *pt, *ptt, t[i].pos); Modify(root, 0, inf, t[i].pos, 0, *ptt); if(s[t[i].num].size() == 2) -- cnt; } else ans[t[i].num] = (cnt == k? Query(t[i].pos): -1); } for(int i = 1; i <= m; ++i) printf("%d\n", ans[i]); return 0; }