洛谷传送门(Qtree)
洛谷传送门(ZJOI)
BZOJ传送门(ZJOI)
(下面我们只讨论Qtree4, ZJOI一题实际是Qtree4简化版)
题目描述
给出一棵边带权的节点数量为 N N 的树,初始树上所有节点都是白色。有两种操作:
- ,改变节点x的颜色,即白变黑,黑变白
- A A ,询问树中最远的两个白色节点的距离,这两个白色节点可以重合(此时距离为)。
输入输出格式
输入格式:
第一行为一个整数 N(N≤100000) N ( N ≤ 100000 ) 。
在以下 N−1 N − 1 行, 每行三个整数 a,b,c a , b , c , 表示标号为 a a 、的点之间有一条长度为 c c 的边。
再下面一行一个整数表示操作组数。
以下 Q Q 行, 每行分别表示一组操作, 格式如上。输出格式:
对于每个操作,输出最远两个白色点的距离。 如果此刻图中没有白色点, 则输出”They have disappeared.”。
解题分析
很容易想到这道题可以用点分树解决。我们要求两个白点之间的最大距离, 可以转化到点分树上一个分治中心的不同子树间距分治中心最大距离的和, 这显然是可以用堆保存分治中心的信息。而修改操作可以利用两个优先队列实现可删除的堆(当然用multiset也是可以的), 爬点分树更新答案。
为了在删除后方便地找到当前最优答案, 我们需要三个堆: sub[i] s u b [ i ] 保存 i i 为分治中心时分治范围内所有点到上层分治中心的距离, 保存每个不同子树中的最优值来更新答案。而对于全局我们可以用一个堆 all a l l 保存各层的最优值, 做到 O(Nlog2(N)) O ( N l o g 2 ( N ) ) 修改, O(log(N)) O ( l o g ( N ) ) 查询。
值得注意的是, 此题中边权可以为负, 所以堆底应该返回 −INTMAX − I N T M A X , 而不是0。
其他小细节见代码:#include <cstdio> #include <algorithm> #include <cstdlib> #include <cctype> #include <cstring> #include <queue> #include <cmath> #include <limits.h> #define R register #define IN inline #define gc getchar() #define W while #define MX 100050 bool neg, vis[MX], col[MX]; struct Heap//可删除的堆 { std::priority_queue <int> i, o; IN void push(const int &tar) {i.push(tar);} IN void erase(const int &tar) {o.push(tar);} IN void pop() { W (!i.empty() && !o.empty() && o.top() == i.top()) o.pop(), i.pop(); i.pop(); } IN int top() { W (!i.empty() && !o.empty() && o.top() == i.top()) o.pop(), i.pop(); return i.empty() ? -INT_MAX : i.top(); } IN int get()//查堆中前两大之和 { int fir = top(); if(fir == -INT_MAX) return 0; pop(); int sec = top(); push(fir); return sec == -INT_MAX ? 0 : sec + fir; } }tp[MX], sub[MX], all; template <class T> IN void in(T &x) { x = 0; R char c = gc; W (!isdigit(c)) {if(c == '-') neg = true; c = gc;} W (isdigit(c)) x = (x << 1) + (x << 3) + c - 48, c = gc; if(neg) neg = false, x = -x; } int dot, q, deal, cot, cnt, root, tot; int mx[MX], siz[MX], ans[MX], dis[22][MX], best[MX], ly[MX], head[MX], fat[MX]; struct Edge { int to, len, nex; }edge[MX << 1]; IN void addedge(const int &from, const int &to, const int &len) { edge[++cnt] = {to, len, head[from]}; head[from] = cnt; } namespace Dtdv { void prework(const int &now, const int &fa, const int &len, const int &layer)//统计当前分治中心管理范围所有点到上层分治中心的答案 { siz[now] = 1; dis[layer][now] = len; //点分树保证深度不会超过log(N), 所以可以开的下。 sub[root].push(len); for (R int i = head[now]; i; i = edge[i].nex) { if(vis[edge[i].to] || edge[i].to == fa) continue; prework(edge[i].to, now, len + edge[i].len, layer); siz[now] += siz[edge[i].to];//为下层点分做准备 } } void getroot(R int now, R int fa) { mx[now] = 0; siz[now] = 1; for (R int i = head[now]; i; i = edge[i].nex) { if(vis[edge[i].to] || fa == edge[i].to) continue; getroot(edge[i].to, now); siz[now] += siz[edge[i].to]; mx[now] = std::max(mx[now], siz[edge[i].to]); } mx[now] = std::max(mx[now], deal - siz[now]); if(mx[now] < mx[root]) root = now; } void build(R int now, R int from, const int &layer, const int &totlen) { int tmp; tp[now].push(0); prework(from, 0, totlen, layer); vis[now] = true, ly[now] = layer; for (R int i = head[now]; i; i = edge[i].nex) { if(vis[edge[i].to]) continue; root = 0; deal = siz[edge[i].to]; getroot(edge[i].to, 0); tmp = root; fat[root] = now; build(root, edge[i].to, layer + 1, edge[i].len); tp[now].push(best[tmp] = sub[tmp].top());//best保存当前层到上层距离最大的值 } all.push(ans[now] = tp[now].get());//ans保存当前层的最优答案 //都是为了方便删除/插入时比较是否有更新而设 //相当于ans记录all中的一个值,best记录tp中的一个值 } void modify(const int &from) { if(col[from]) ++tot, tp[from].push(0); else --tot, tp[from].erase(0); int tmp, anss = tp[from].get(); int now = fat[from]; int son = from; if(anss != ans[from])//可以更新答案 all.erase(ans[from]), all.push(anss), ans[from] = anss; W (now) { if(col[from]) sub[son].push(dis[ly[son]][from]); else sub[son].erase(dis[ly[son]][from]); tmp = sub[son].top(); if(tmp != best[son])//可以更新 { if(best[son] != -INT_MAX) tp[now].erase(best[son]); if(tmp != -INT_MAX) tp[now].push(tmp); //注意不能删掉堆底 best[son] = tmp; } anss = tp[now].get(); if(ans[now] != anss) all.erase(ans[now]), all.push(anss), ans[now] = anss; now = fat[son = now]; } col[from] ^= 1; } } int main(void) { int a, b, c; char buf[5]; in(dot); tot = mx[root = 0] = deal = dot; for (R int i = 1; i < dot; ++i) in(a), in(b), in(c), addedge(a, b, c), addedge(b, a, c); in(q); Dtdv::getroot(1, 0); Dtdv::build(root, 1, 0, 0); W (q--) { scanf("%s", buf + 1); if(buf[1] == 'A') if(!tot) printf("They have disappeared.\n"); else printf("%d\n", all.top()); else scanf("%d", &a), Dtdv::modify(a); } }
ZJOI那道题等于是把路径权值全部设为1了而已, 改一改就可以过…