P4115 Qtree4
点分树,堆
原题面:Luogu。
简述
给定一棵 \(n\) 个节点的的树,边有边权。初始树上所有节点都是白色。给定 \(m\) 次操作:
- 反转给定点的颜色。
- 询问树上最远的两个白色节点的距离,两点可以重合(距离为 0)。
\(1\le n\le 10^5\),\(1\le m\le 2\times 10^5\),\(-10^3\le\) 边权 \(\le 10^3\)。
1S,512MB。
分析
预处理 \(\operatorname{lca}\) 后即可快速求得两点距离,先考虑如何处理静态问题。可以考虑在点分治的过程中对每个节点都维护一个堆,储存分治块内该点子树节点中的所有白点到重心的距离,使用其中的分属不同分治块的最大值与次大值拼接路径求最大值即可,单次询问时间复杂度 \(O(n\log^2 n)\) 级别。
考虑动态问题,把上述过程放到点分树上自底向下进行。对于每个节点预处理两个堆:\(t_1\) 维护该点子树内所有白点到父亲的距离(对应点分治中求得各点到重心最大值),\(t_2\) 维护所有儿子的 \(t_1\) 的最大值(对应维护分属不同分治块的最值过程)。特别地,若 \(u\) 为白点,则 \(0 \in t_1(u)\)。即有:
同时处理一个堆 \(all\),储存所有节点路径拼接后的值,即有 \(all = \{ \max(t_2(u)) + \operatorname{secmax}(t_2(u))\}\),每次询问的答案即 \(\max(all)\)。初始时所有节点均为白点,在建立点分树时对每个节点初始化上述三个堆即可,复杂度 \(O(n\log^2 n)\) 级别。
对于修改操作,自指定节点向上跳父亲,在 \(t_1\) 中添加/删除父亲到指定节点的链,并更新父亲的 \(t_2\) 与 \(all\) 即可,单次修改复杂度 \(O(\log^2 n)\) 级别。注意添加/删除指定节点 \(t_2\) 中的 \(0\)。上述过程中的堆需要支持插入、删除、查询最大值、次大值,可以使用 mulitiset
或堆+懒惰删除实现。
特别地,此题的树上可能有负权边且允许白点重合。答案要对 \(0\) 取最大值。
总时间复杂度是常数极大的 \(O((n + m)\log^2 n)\)。
然后是一些卡常技巧:树剖求 \(\operatorname{lca}\) 常数较小,实际表现比 RMQ 更优;multiset
常数过大不如堆 + 懒惰删除。但还是被色批 OJ 卡常了呜呜,下面的代码只能在 Luogu 上过。
代码
//知识点:点分树
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <queue>
#define LL long long
const int kN = 1e5 + 10;
const int kM = kN << 1;
const int kInf = 1e9 + 2077;
//=============================================================
int n, m, e_num, head[kN], v[kM], w[kM], ne[kM], dep[kN];
int cnt, root, sumsz, sz[kN], maxsz[kN], newfa[kN];
bool vis[kN], val[kN];
//=============================================================
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 Chkmax(int &fir_, int sec_) {
if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
if (sec_ < fir_) fir_ = sec_;
}
void AddEdge(int u_, int v_, int w_) {
v[++ e_num] = v_, w[e_num] = w_;
ne[e_num] = head[u_];
head[u_] = e_num;
}
namespace Cut {
int fa[kN], son[kN], dep[kN], dis[kN], size[kN], top[kN];
void Dfs1(int u_, int fa_) {
fa[u_] = fa_;
size[u_] = 1;
dep[u_] = dep[fa_] + 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (v_ == fa_) continue ;
dis[v_] = dis[u_] + w_;
Dfs1(v_, u_);
if (size[v_] > size[son[u_]]) son[u_] = v_;
size[u_] += size[v_];
}
}
void Dfs2(int u_, int top_) {
top[u_] = top_;
if (son[u_]) Dfs2(son[u_], top_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa[u_] or v_ == son[u_]) continue ;
Dfs2(v_, v_);
}
}
int Lca(int u_, int v_) {
for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
if (dep[top[u_]] < dep[top[v_]]) {
std::swap(u_, v_);
}
}
return dep[u_] < dep[v_] ? u_ : v_;
}
int Dis(int u_, int v_) {
return dis[u_] + dis[v_] - 2 * dis[Lca(u_, v_)];
}
void Prepare() {
Dfs1(1, 0), Dfs2(1, 1);
}
}
struct Heap { //懒惰删除堆
std::priority_queue <int> heap, delt;
void Insert(int val_) {
heap.push(val_);
}
void Erase(int val_) {
delt.push(val_);
}
int Size() {
return heap.size() - delt.size();
}
void Update() {
while (delt.size() && heap.top() == delt.top()) {
heap.pop();
delt.pop();
}
}
int Top() {
Update();
return Size() ? heap.top() : -kInf;
}
int Sectop() {
int t1 = Top(), t2;
Update(), heap.pop();
t2 = Top();
Insert(t1);
return t2;
}
int Get() {
if (Size() >= 2) return Top() + Sectop();
if (Size() == 1) return std::max(Top(), 0);
return -kInf;
}
} all, dis[kN], disfa[kN];
void AddAll(int pos_) {
if (dis[pos_].Size() >= 2) all.Insert(dis[pos_].Get());
}
void DeleteAll(int pos_) {
if (dis[pos_].Size() >= 2) all.Erase(dis[pos_].Get());
}
void CalcSize(int u_, int fa_) { //找重心
sz[u_] = 1, maxsz[u_] = 0;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
if (v_ == fa_ || vis[v_]) continue;
CalcSize(v_, u_);
Chkmax(maxsz[u_], sz[v_]);
sz[u_] += sz[v_];
}
Chkmax(maxsz[u_], sumsz - sz[u_]);
if (maxsz[u_] < maxsz[root]) root = u_;
}
void CalcDis(int u_, int fa_, int dis_, int pos_) {
disfa[pos_].Insert(dis_); //预处理
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (v_ == fa_ || vis[v_]) continue;
CalcDis(v_, u_, dis_ + w_, pos_);
}
}
void Dfs(int u_, int fa_) { //建立点分树
vis[u_] = true;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (v_ == fa_ || vis[v_]) continue;
sumsz = sz[v_], root = 0, maxsz[root] = kN;
CalcSize(v_, u_);
CalcDis(v_, u_, w_, root);
newfa[root] = u_;
dis[u_].Insert(disfa[root].Top()); //预处理
CalcSize(root, 0), Dfs(root, 0);
}
dis[u_].Insert(0);
AddAll(u_);
}
void Add(int pos_) { //添加白点
++ cnt;
DeleteAll(pos_); //以当前节点为一端点的情况
dis[pos_].Insert(0);
AddAll(pos_);
for (int now_ = pos_; newfa[now_]; now_ = newfa[now_]) {
int f = newfa[now_], d = Cut::Dis(pos_, f);
DeleteAll(f); //削除更新前的贡献
if (disfa[now_].Size()) dis[f].Erase(disfa[now_].Top());
disfa[now_].Insert(d); //插入父亲到指定节点的链
if (disfa[now_].Size()) dis[f].Insert(disfa[now_].Top());
AddAll(f); //更新贡献
}
}
void Delete(int pos_) { //删除白点
-- cnt;
DeleteAll(pos_);
dis[pos_].Erase(0);
AddAll(pos_);
for (int now_ = pos_; newfa[now_]; now_ = newfa[now_]) {
int f = newfa[now_], d = Cut::Dis(pos_, f);
DeleteAll(f); //削除更新前的贡献
if (disfa[now_].Size()) dis[f].Erase(disfa[now_].Top());
disfa[now_].Erase(d); //插入父亲到指定节点的链
if (disfa[now_].Size()) dis[f].Insert(disfa[now_].Top());
AddAll(f); //更新贡献
}
}
void Modify(int pos_) { //单点修改,更新点分树上该点到根路径上各点的 子树信息
val[pos_] ? Add(pos_) : Delete(pos_);
val[pos_] ^= 1;
}
int Query() {
return std::max(all.Top(), 0);
}
//=============================================================
int main() {
cnt = n = read();
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_), AddEdge(v_, u_, w_);
}
Cut::Prepare();
sumsz = n, root = 0, maxsz[root] = kN;
CalcSize(1, 0), CalcSize(root, 0), Dfs(root, 0);
m = read();
for (int i = 1; i <= m; ++ i) {
char opt[5]; scanf("%s", opt + 1);
if (opt[1] == 'C') {
Modify(read());
} else {
if (!cnt) {
printf("They have disappeared.\n");
} else {
printf("%d\n", cnt == 1 ? 0 : Query());
}
}
}
return 0;
}