Description

若带点权、边权的树上一对 \((u, v)\) 为 friend,那么需要满足 \(\text{dist}(u, v) \le r_u + r_v\),其中 \(r_x\) 为点 \(x\) 的权,\(\text{dist}(u, v)\) 表示 \(u, v\) 的树上距离,即 \(u, v\) 间的简单路径上的边权和。

一开始树为空,之后有 \(n\) 次加点操作,每次各处该点需要连接的结点、点权以及边权。对于每次加点之后得到的树,你需要输出当前树上 friends 的对数。强制在线。

Hint

\(1\le n\le 10^5, 1\le \text{边权}\le 10^4, 1\le \text{点权}\le 10^9\)。

Solution

这里的加点操作非常难搞,因此不妨试着先离线。如果可以离线,我们可以先把树建出来,一开始 \(\forall i\in[1, n],r_i\leftarrow -\infty\),然后一个个将点权修改。这就不难用 动态点分治 维护。

考虑将原来的式子进行变形:\(\text{dist}(u, v) \le r_u + r_v \quad\longrightarrow\quad r_v - \text{dist}(u, v) \le -r_u\),然后就是对于每个 \(u\) 数一数满足条件的 \(v\) 的个数。

这比较显然可以用 平衡树 维护,不过在跑点分树的过程中,如果直接将答案加上点分子树的贡献,会与其祖先的答案算重。于是再对点分树父亲维护一颗平衡树用作 容斥。这样一次修改是 \(O(\log^2 n)\) 的,而答案显然可以在修改时计算影响。

于是现在我们有 \(O(n\log^2 n)\) 的离线算法了。


考虑在线化改造这个算法。如果不做改动,每次插入建一次点分树的话复杂度会变成 \(O(n^2\log^2 n)\);而如果插入节点后干脆不对点分树做改动的话,众所周知仍然会被卡,不该也不行。

于是尝试均摊一下这两部分。如果我们选择根号重构,那么树高只能控制在 \(O(\sqrt{n})\),这样复杂度为 \(O(n^{1.5}\log n)\),并不理想。

但如果选择替罪羊式重构的话,我们的树高就能被控制在 \(O(\log n)\)。所谓替罪羊式重构,就是对于一个结点如果它的某一子树的大小占据的整颗子树的 \(\alpha(\approx 0.8)\) 倍,那么就部分重构这颗子树。

这样的复杂度是均摊 \(O(n\log^2 n)\) 的,并且做到了在线。


写起来真的非常复杂,而且需要注意常数。优化技巧:

  1. 使用较快的平衡树,不要用 Splay 或 FHQ-Treap;
  2. 计算树上距离不需要再写一个 LCA,直接记录每个点到祖先的距离即可。因为一个点的祖先数不超过 \(O(\log n)\);
  3. 重构选择深度最浅的重构点进行重构;
  4. Fast IO method。

Code

附上 Luogu 上卡到 Page 1(2020.10.14)的代码:

/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : WC2014 紫荆花之恋
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector> using namespace std;
const int N = 1e5 + 5;
const int mod = 1e9;
const int LogN = 18; struct IO {
static const int S=1<<24;
char buf[S],*p1,*p2;int st[105],Top;
~IO(){clear();}
inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(const char c){Top==S&&(clear(),0);buf[Top++]=c;}
inline char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
IO&operator >> (char&x){while(x=gc(),x==' '||x=='\n');return *this;}
template<typename T> IO&operator >> (T&x){
x=0;bool f=0;char ch=gc();
while(ch<'0'||ch>'9'){if(ch=='-') f^=1;ch=gc();}
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=gc();
f?x=-x:0;return *this;
}
IO&operator << (const char c){pc(c);return *this;}
template<typename T> IO&operator << (T x){
if(x<0) pc('-'),x=-x;
do{st[++st[0]]=x%10,x/=10;}while(x);
while(st[0]) pc('0'+st[st[0]--]);
return *this;
}
} fin, fout; #define alpha 0.7
namespace bst_nodes { // 奇怪的平衡树实现:不平衡就旋转,这样跑的很快
struct Node {
int ch[2], val, cnt, siz;
inline Node() { }
inline Node(int l, int r, int v, int c, int s) :
val(v), cnt(c), siz(s) { ch[0] = l, ch[1] = r; }
} t[N << 6];
int total = 0;
int rec[N << 6], top = 0; inline void create(int& x, int v) {
x = top ? rec[top--] : ++total;
t[x] = Node(0, 0, v, 1, 1);
}
inline void pushup(int& x) {
t[x].siz = t[x].cnt + t[t[x].ch[0]].siz + t[t[x].ch[1]].siz;
}
inline void rotate(int& x, int d) {
int y = t[x].ch[d];
if (t[x].siz * alpha > t[y].siz) return;
t[x].ch[d] = t[y].ch[!d];
t[y].ch[!d] = x, pushup(x), pushup(x = y);
}
void insert(int& x, int v) {
if (!x) return create(x, v), void();
++t[x].siz;
if (v == t[x].val) return ++t[x].cnt, void();
int d = v > t[x].val;
insert(t[x].ch[d], v), rotate(x, d);
}
int count(int x, int v) {
if (!x) return 0;
if (t[x].val <= v) return t[t[x].ch[0]].siz + t[x].cnt + count(t[x].ch[1], v);
else return count(t[x].ch[0], v);
}
void recycle(int& x) {
if (!x) return;
recycle(t[x].ch[0]), recycle(t[x].ch[1]);
rec[++top] = x, x = 0;
}
}
struct bst {
int root;
bst() : root(0) { }
inline void insert(int v) { bst_nodes::insert(root, v); }
inline void clear() { bst_nodes::recycle(root); }
inline int count(int v) { return bst_nodes::count(root, v); }
}; int n, Q, R[N];
long long ans = 0ll;
struct Edge {
int to, len;
Edge(int t, int l) : to(t), len(l) { }
}; vector<Edge> adj[N]; struct {
int fa[N], cnt[N], dep[N];
long long anc[N][LogN << 2];
bst t[N][2]; bool vis[N];
int siz[N], maxp[N], ctr; inline long long dist(int x, int a) {
return anc[x][dep[a]];
}
int getSize(int x, int f) {
siz[x] = 1;
for (auto y : adj[x]) if (y.to != f && !vis[y.to])
siz[x] += getSize(y.to, x);
return siz[x];
}
void getCentr(int x, int f, int t) {
maxp[x] = 0;
for (auto y : adj[x]) if (y.to != f && !vis[y.to])
getCentr(y.to, x, t), maxp[x] = max(maxp[x], siz[y.to]);
maxp[x] = max(maxp[x], t - siz[x]);
if (maxp[ctr] > maxp[x]) ctr = x;
} void getBst(int s, int x, int f, long long d) {
t[s][0].insert(d - R[x]);
if (fa[s]) t[s][1].insert(dist(x, fa[s]) - R[x]);
for (auto y : adj[x]) if (y.to != f && !vis[y.to])
getBst(s, y.to, x, d + y.len);
}
void getDist(int s, int x, int f, long long d) {
anc[x][dep[s]] = d;
for (auto y : adj[x]) if (y.to != f && !vis[y.to])
getDist(s, y.to, x, d + y.len);
}
int rebuild(int x, int f, int d) {
maxp[ctr = 0] = N;
getCentr(x, 0, getSize(x, 0));
int s = ctr;
vis[s] = 1, fa[s] = f, dep[s] = d, cnt[s] = 1;
t[s][0].clear(), t[s][1].clear(); getBst(s, s, 0, 0);
getDist(s, s, 0, 0);
for (auto y : adj[s]) if (!vis[y.to])
cnt[s] += cnt[rebuild(y.to, s, d + 1)];
return vis[s] = 0, s;
} inline long long update(int i, int f, int w) {
fa[i] = f, dep[i] = dep[f] + 1;
t[i][0].insert(-R[i]);
for (int j = 1; j <= dep[f]; j++)
anc[i][j] = anc[f][j] + w; if (i == 1) return 0ll;
adj[f].emplace_back(i, w);
adj[i].emplace_back(f, w); int target = 0;
for (int x = i, y = fa[x]; y; y = fa[x = y]) {
long long dis = R[i] - dist(i, y);
ans += t[y][0].count(dis) - t[x][1].count(dis);
t[y][0].insert(-dis), t[x][1].insert(-dis);
if (++cnt[y] * alpha < cnt[x]) target = y;
} if (target) {
for (int x = fa[target]; x; x = fa[x]) vis[x] = 1;
rebuild(target, fa[target], dep[target]);
for (int x = fa[target]; x; x = fa[x]) vis[x] = 0;
} return ans;
}
} vdct;
#undef alpha signed main() {
fin >> n, fin >> n;
for (int i = 1, f, w; i <= n; i++) {
fin >> f >> w >> R[i], f ^= ans % mod;
fout << vdct.update(i, f, w) << '\n';
}
}

【WC2014】紫荆花之恋(替罪羊重构点分树 & 平衡树)的更多相关文章

  1. 【BZOJ3435】[Wc2014]紫荆花之恋 替罪点分树+SBT

    [BZOJ3435][Wc2014]紫荆花之恋 Description 强强和萌萌是一对好朋友.有一天他们在外面闲逛,突然看到前方有一棵紫荆树.这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从 ...

  2. 【bzoj3435】[Wc2014]紫荆花之恋 替罪点分树套SBT

    题目描述 强强和萌萌是一对好朋友.有一天他们在外面闲逛,突然看到前方有一棵紫荆树.这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从紫荆树上长了出来.仔细看看的话,这个大树实际上是一个带权树.每 ...

  3. bzoj 3435: [Wc2014]紫荆花之恋 替罪羊树维护点分治 && AC400

    3435: [Wc2014]紫荆花之恋 Time Limit: 240 Sec  Memory Limit: 512 MBSubmit: 159  Solved: 40[Submit][Status] ...

  4. BZOJ 3435 / Luogu 3920 [WC2014]紫荆花之恋 (替罪羊树 动态点分治 套 Treap)

    题意 略 分析 引用PoPoQQQ的话 吾辈有生之年终于把这道题切了...QAQ (蒟蒻狂笑) Orz PoPoQQQ,我又抄PoPoQQQ的题解了 - 突然发现有旋Treap没那么难写 学习了一波C ...

  5. 半小时写完替罪羊重构点分树做动态动态点分治之紫荆花之恋的wyy贴心指导

    刷题训练 初学者 有一定语言基础,但是不了解算法竞赛,水平在联赛一等奖以下的. 参考书:<算法竞赛入门经典--刘汝佳>,<算法竞赛入门经典训练指南--刘汝佳> 题库:洛谷(历年 ...

  6. BZOJ3435: [Wc2014]紫荆花之恋(替罪羊树,Treap)

    Description 强强和萌萌是一对好朋友.有一天他们在外面闲逛,突然看到前方有一棵紫荆树.这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从紫荆树上长了出来.仔细看看的话,这个大树实际上是 ...

  7. luogu P3920 [WC2014]紫荆花之恋

    LINK:紫荆花之恋 每次动态加入一个节点 统计 有多少个节点和当前节点的距离小于他们的权值和. 显然我们不能n^2暴力. 考虑一个简化版的问题 树已经给出 每次求某个节点和其他节点的贡献. 不难想到 ...

  8. BZOJ 3435: [Wc2014]紫荆花之恋

    二次联通门 : BZOJ 3435: [Wc2014]紫荆花之恋 二次联通门 : luogu P3920 [WC2014]紫荆花之恋 /* luogu P3920 [WC2014]紫荆花之恋 怀疑人生 ...

  9. BZOJ3435[Wc2014]紫荆花之恋——动态点分治(替罪羊式点分树套替罪羊树)

    题目描述 强强和萌萌是一对好朋友.有一天他们在外面闲逛,突然看到前方有一棵紫荆树.这已经是紫荆花飞舞的季节了,无数的花瓣以肉眼可见的速度从紫荆树上长了出来.仔细看看的话,这个大树实际上是一个带权树.每 ...

随机推荐

  1. 僵尸进程与SIGCHLD信号

    什么是僵尸进程? 首先内核会释放终止进程(调用了exit系统调用)所使用的所有存储区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息.这些信息至少包括进程ID,进程的终止状态,以及该 ...

  2. exec系列函数详解

    execve替换进程映像(加载程序):execve系统调用,意味着代码段.数据段.堆栈段和PCB全部被替换.在UNIX中采用一种独特的方法,它将进程创建与加载一个新进程映像分离.这样的好处是有更多的余 ...

  3. 剑指offer刷题(算法类_1)

    斐波那契数列 007-斐波拉契数列 题目描述 题解 代码 复杂度 008-跳台阶 题目描述 题解 代码 复杂度 009-变态跳台阶 题目描述 题解 代码 复杂度 010-矩形覆盖 题目描述 题解 代码 ...

  4. shell简介及变量的定义查看撤销

    1.shell分类及相关软件  图形界面Shell(Graphical User Interface shell 即 GUI shell),如:GNOME.KDE 命令行式Shell(Command ...

  5. http请求返回ObjectJson,Array之类转换类型

    以下所说的类来自:package com.alibaba.fastjson 1,形如以下返回,其实是个json的map形式的返回 { "success": true, " ...

  6. Spring之事务源码

    对@Transactional注解的类进行动态代理 同前文<Spring AOP源码分析>中分析动态代理入口一样,都是在initializeBean时执行. Object exposedO ...

  7. 工作流(workflow)

    1,JBPM 工作流(开源历史悠久) 2,activity 工作流(开源历史悠久) 3,workable 工作流(功能比较强大,但是开源维护缓慢,比较注重商业化) 以上三个是主流的工作流

  8. 利用requestes\pyquery\BeautifulSoup爬取某租房公寓(深圳市)4755条租房信息及总结

    为了分析深圳市所有长租.短租公寓的信息,爬取了某租房公寓深圳区域所有在租公寓信息,以下记录了爬取过程以及爬取过程中遇到的问题: 爬取代码: 1 import requests 2 from reque ...

  9. MathType怎么写分段函数?

    分段函数是数学里面特有的一种函数,它是对于自变量x的不同的取值范围,有着不同的解析式的函数.它的特点就是有一个大括号,然后有至少2个函数解析式,写这样的函数离不开专业的公式编辑器,下面就来学习具体编辑 ...

  10. guitar pro系列教程(二十):Guitar Pro使用技巧之使用向导

    本章节将采用图文结合的方式为大家讲述{cms_selflink page='index' text='Guitar Pro'}使用技巧里面的使用向导的相关知识,有兴趣的朋友可以一起来学习哦. 当你创建 ...