题目链接

https://www.lydsy.com/JudgeOnline/problem.php?id=4732

题解

首先,一个正确性比较显然的结论是:对于一棵有根树上的两条链 \((x_1, y_1)\) 与 \((x_2, y_2)\),若两条链存在交点,必然有:\({\rm lca}_{x_1, y_1}\) 在链 \((x_2, y_2)\) 上,或者 \({\rm lca}_{x_2, y_2}\) 在链 \((x_1, y_1)\) 上。

这样,我们可以令 \(a_u\) 表示「链的两端点的 \(\rm lca\) 为点 \(u\) 」的链的权值和,\(b_u\) 表示「链经过点 \(u\) 但两端点的 \(\rm lca\) 不为点 \(u\) 」的链的权值和。那么:

  • 对于链的插入操作,插入权值为 \(w\) 的链 \((x, y)\) 时,我们只需使 \(a_{{\rm lca}_{x, y}}\) 增加 \(w\),链 \((x, y)\) 上除 \({\rm lca}_{x, y}\) 的所有点的 \(b\) 增加 \(w\)
  • 对于链的删除操作,我们只需将链的权值改为 \(-w\) 即可,其余操作同插入操作
  • 对于查询操作,根据最开始给出的结论,对于一条路径 \((x, y)\),所能得到的权值和即为路径上所有结点的 \(a\) 数值之和再加上 \(b_{{\rm lca}_{x, y}}\)

为了方便,我们将树链剖分后结点 \(u\) 的所有子结点中,通过轻边与结点 \(u\) 相连的称为 \(u\) 的轻儿子。

首先我们考虑如何查询答案。将原树树链剖分之后,任意一条路径都可以分成三部分。令整条路径深度最小的点为 \(u\),和 \(u\) 在同一条重链上且深度最大的点为 \(v\),那么整条路径可分为:由 \(u\) 的某个轻儿子引出的一条链(可以为空)+\(u\) 至 \(v\) 的重链部分+由 \(v\) 的某个轻儿子引出的一条链(可以为空)。我们令 \(g_u\) 表示由点 \(u\) 的某个轻儿子引出的链的 \(\sum a\) 的最大值,那么一条路径的答案即为:\(g_u + g_v + b_u + w_{u, v}\)(注意这是 \(u \neq v\) 时的情况,当 \(u = v\) 时取的是点 \(u\) 的所有轻儿子引出的链的 \(\sum a\) 的最大值与次大值)。其中,\(w_{u, v}\) 表示点 \(u\) 到点 \(v\) 的路径上所有点的 \(a\) 数值之和。

除去答案式子中的 \(b_u\),那么对于一条重链而言,这就是一个连续最大和的形式,只不过两端点还应该加上 \(g_u\) 与 \(g_v\)。这样,我们就可以用线段树来维护区间连续最大和了,对于两端的 \(g\),我们在记录左右最值时处理一下即可。由于查询的是整棵树的最大权路径,我们可以用一个 multiset 来储存每条重链的连续最大和,每次查询时查询 multiset 内的最大值即可。

接下来考虑链的插入操作(删除也可看做插入权值为负的链),根据 \(a, b\) 数组的定义,每次插入一条链后 \(a\) 只需进行单点修改。而 \(b\) 数组则为区间修改(链上修改),不过注意到答案式子 \(g_u + g_v + b_u + w_{u, v}\) 中仅有 \(u\) 一个结点使用了 \(b\) 数组,换句话说,修改 \(b\) 只会对区间的左端点有影响,因此和普通的区间加操作一样,在线段树上记录区间加标记再下传即可。注意随着链上 \(a\) 数组的修改,链到根所有轻重链交替处结点的 \(g\) 数组也要修改。同时还要顺便修改答案的 multiset。

时间复杂度 \(O(n \log^2 n)\)。

代码

#include<bits/stdc++.h>

using namespace std;

#define rg register

typedef long long ll;

template<typename T> inline bool checkMax(T& a, const T& b) {
return a < b ? a = b, true : false;
} const int N = 1e5 + 10; struct Edge {
Edge* next;
int to;
Edge () {}
Edge (Edge* next, int to): next(next), to(to) {}
} *first[N], pool[N << 1], *pis = pool; inline void add(int u, int v) {
first[u] = new (pis++) Edge (first[u], v);
first[v] = new (pis++) Edge (first[v], u);
} int n, m, size[N], pa[N], heavy[N], top[N], dfn[N], arc_dfn[N], dfn_cnt, end_p[N], dep[N];
multiset<ll> ans, g[N]; inline void dfs1(int u, int pa) {
size[u] = 1;
int v = 0;
for (Edge* now = first[u]; now; now = now->next) {
if (now->to ^ pa) {
::pa[now->to] = u;
dep[now->to] = dep[u] + 1;
dfs1(now->to, u);
size[u] += size[now->to];
if (checkMax(v, size[now->to])) {
heavy[u] = now->to;
}
}
}
} inline void dfs2(int u, int t) {
top[u] = t;
dfn[u] = end_p[u] = ++dfn_cnt, arc_dfn[dfn_cnt] = u;
if (heavy[u]) {
dfs2(heavy[u], t);
end_p[u] = end_p[heavy[u]];
} else {
ans.insert(0);
}
for (Edge* now = first[u]; now; now = now->next) {
if (now->to ^ pa[u] && now->to ^ heavy[u]) {
g[u].insert(0);
dfs2(now->to, now->to);
}
}
} #define lo (o<<1)
#define ro (o<<1|1) struct State {
ll lv, rv, maxv, sumv;
State () {
lv = rv = maxv = sumv = 0;
}
inline State operator + (const State& a) const {
State res;
res.lv = max(lv, sumv + a.lv);
res.rv = max(a.rv, a.sumv + rv);
res.sumv = sumv + a.sumv;
res.maxv = max(max(maxv, a.maxv), rv + a.lv);
return res;
}
} s[N << 2]; ll a[N << 2], addv[N << 2]; inline void add_tag(int o, ll v) {
addv[o] += v;
s[o].rv += v;
s[o].maxv += v;
} inline void push_down(int o) {
if (addv[o]) {
add_tag(lo, addv[o]);
add_tag(ro, addv[o]);
addv[o] = 0;
}
} inline void modify_s(int l, int r, int o, int p, ll v) {
if (l == r) {
int u = arc_dfn[l];
a[o] += v, s[o].sumv = a[o];
multiset<ll>:: iterator it = g[u].end();
ll res = 0;
if (g[u].size() >= 1) {
res += *--it;
}
s[o].lv = a[o] + res;
s[o].rv = a[o] + res + addv[o];
if (g[u].size() >= 2) {
res += *--it;
}
s[o].maxv = res + a[o] + addv[o];
} else {
int mid = l + r >> 1;
push_down(o);
(p <= mid) ? modify_s(l, mid, lo, p, v) : modify_s(mid + 1, r, ro, p, v);
s[o] = s[lo] + s[ro];
}
} inline void modify_tag(int l, int r, int o, int ql, int qr, ll v) {
if (ql <= l && r <= qr) {
return add_tag(o, v);
} else {
int mid = l + r >> 1;
push_down(o);
if (ql <= mid) {
modify_tag(l, mid, lo, ql, qr, v);
} if (qr > mid) {
modify_tag(mid + 1, r, ro, ql, qr, v);
}
s[o] = s[lo] + s[ro];
}
} inline State query(int l, int r, int o, int ql, int qr) {
if (ql <= l && r <= qr) {
return s[o];
} else {
int mid = l + r >> 1;
push_down(o);
if (qr <= mid) {
return query(l, mid, lo, ql, qr);
} else if (ql > mid) {
return query(mid + 1, r, ro, ql, qr);
} else {
return query(l, mid, lo, ql, qr) + query(mid + 1, r, ro, ql, qr);
}
}
} inline void jump(int u, ll tmp, int lca, int w) {
for (; u; u = pa[top[u]]) {
int l = lca ? max(dfn[lca] + 1, dfn[top[u]]) : n + 1, r = dfn[u];
int p = pa[top[u]];
if (p) {
State x = query(1, n, 1, dfn[top[p]], end_p[p]);
ans.erase(ans.lower_bound(x.maxv));
g[p].erase(g[p].lower_bound(tmp));
tmp = x.lv;
}
if (l <= r) {
ans.erase(ans.lower_bound(query(1, n, 1, dfn[top[u]], end_p[u]).maxv));
modify_tag(1, n, 1, l, r, w);
ans.insert(query(1, n, 1, dfn[top[u]], end_p[u]).maxv);
}
if (p) {
g[p].insert(query(1, n, 1, dfn[top[u]], end_p[u]).lv);
modify_s(1, n, 1, dfn[p], 0);
ans.insert(query(1, n, 1, dfn[top[p]], end_p[p]).maxv);
}
}
} inline void modify(int u, int v, int w) {
int old_u = u, old_v = v, lca;
for (; top[u] ^ top[v]; u = pa[top[u]]) {
if (dep[top[u]] < dep[top[v]]) {
swap(u, v);
}
}
lca = dep[u] <= dep[v] ? u : v;
u = old_u, v = old_v;
jump(u, query(1, n, 1, dfn[top[u]], end_p[u]).lv, lca, w);
jump(v, query(1, n, 1, dfn[top[v]], end_p[v]).lv, lca, w);
State x = query(1, n, 1, dfn[top[lca]], end_p[lca]);
ans.erase(ans.lower_bound(x.maxv));
modify_s(1, n, 1, dfn[lca], w);
ans.insert(query(1, n, 1, dfn[top[lca]], end_p[lca]).maxv);
jump(lca, x.lv, 0, 0);
} int req_u[N], req_v[N], req_w[N]; int main() {
scanf("%d%d", &n, &m);
for (rg int i = 1; i < n; ++i) {
int u, v; scanf("%d%d", &u, &v);
add(u, v);
}
dfs1(1, 0);
dfs2(1, 1);
for (rg int i = 1; i <= m; ++i) {
char cmd[4];
scanf("%s", cmd);
if (*cmd == '+') {
scanf("%d%d%d", &req_u[i], &req_v[i], &req_w[i]);
modify(req_u[i], req_v[i], req_w[i]);
} else {
int tim; scanf("%d", &tim);
modify(req_u[tim], req_v[tim], -req_w[tim]);
}
printf("%lld\n", *ans.rbegin());
}
return 0;
}

BZOJ4732. [清华集训2016]数据交互(树链剖分+线段树+multiset)的更多相关文章

  1. BZOJ4551[Tjoi2016&Heoi2016]树——dfs序+线段树/树链剖分+线段树

    题目描述 在2016年,佳媛姐姐刚刚学习了树,非常开心.现在他想解决这样一个问题:给定一颗有根树(根为1),有以下 两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均 ...

  2. BZOJ4127Abs——树链剖分+线段树

    题目描述 给定一棵树,设计数据结构支持以下操作 1 u v d 表示将路径 (u,v) 加d 2 u v 表示询问路径 (u,v) 上点权绝对值的和 输入 第一行两个整数n和m,表示结点个数和操作数 ...

  3. BZOJ2819Nim——树链剖分+线段树+Nim游戏

    题目描述 著名游戏设计师vfleaking,最近迷上了Nim.普通的Nim游戏为:两个人进行游戏,N堆石子,每回合可以取其中某一堆的任意多个,可以取完,但不可以不取.谁不能取谁输.这个游戏是有必胜策略 ...

  4. BZOJ2157旅游——树链剖分+线段树

    题目描述 Ray 乐忠于旅游,这次他来到了T 城.T 城是一个水上城市,一共有 N 个景点,有些景点之间会用一座桥连接.为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路 ...

  5. POJ3237 Tree 树链剖分 线段树

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - POJ3237 题意概括 Description 给你由N个结点组成的树.树的节点被编号为1到N,边被编号为1 ...

  6. fzu 2082 过路费 (树链剖分+线段树 边权)

    Problem 2082 过路费 Accept: 887    Submit: 2881Time Limit: 1000 mSec    Memory Limit : 32768 KB  Proble ...

  7. BZOJ.1036 [ZJOI2008]树的统计Count ( 点权树链剖分 线段树维护和与最值)

    BZOJ.1036 [ZJOI2008]树的统计Count (树链剖分 线段树维护和与最值) 题意分析 (题目图片来自于 这里) 第一道树链剖分的题目,谈一下自己的理解. 树链剖分能解决的问题是,题目 ...

  8. 【bzoj1959】[Ahoi2005]LANE 航线规划 树链剖分+线段树

    题目描述 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算 ...

  9. 【BZOJ-2325】道馆之战 树链剖分 + 线段树

    2325: [ZJOI2011]道馆之战 Time Limit: 40 Sec  Memory Limit: 256 MBSubmit: 1153  Solved: 421[Submit][Statu ...

随机推荐

  1. yocto-sumo源码解析(八): ProcessServer

    从前面章节的论述中,我们知道BitBakeServer实际上是一个ProcessServer,什么是ProcessServer不可不了解. 1. 类的声明: 首先这是一个python的多进程包里面的进 ...

  2. 从零系列--开发npm包(二)

    一.利用shell简化组合命令 set -e CVERSION=$(git tag | ) echo "current version:$CVERSION" echo " ...

  3. Protocol buffer的使用案例

    Protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台.google 提供了多种语言的实现:java.c#.c++.go 和 python,每一种实 ...

  4. 高可用OpenStack(Queen版)集群-14.Openstack集成Ceph准备

    参考文档: Install-guide:https://docs.openstack.org/install-guide/ OpenStack High Availability Guide:http ...

  5. Kaggle 广告转化率预测比赛小结

    20天的时间参加了Kaggle的 Avito Demand Prediction Challenged ,第一次参加,成绩离奖牌一步之遥,感谢各位队友,学到的东西远比成绩要丰硕得多.作为新手,希望每记 ...

  6. 不用U盘,用一台好电脑给另一个电脑重装windows10

    先把坏电脑硬盘拆下来,然后挂到好电脑上 把这块盘用系统的磁盘管理工具改成GPT分区表格式,然后整盘分区(NTFS). 再对这个分区进行压缩卷操作,分出第二个区(FAT32格式 大小大于5G 我这里用了 ...

  7. 2018软工实践—Beta冲刺(2)

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Beta 冲鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调组内工作 修改前端界面 展示GitHub当日代码/文档签入记录(组内 ...

  8. 关于char存储值表示

    char里面-128的二进制表示为1000 0000,0的二进制表示为0000 0000 -127的二进制表示为1000 0001, 127的二进制表示为0111 1111. 从-127到-1和1到1 ...

  9. 微信小程序倒计时实现

    思路:跟一般js倒计时一样,主要在于this的变相传递. 实现效果: wxml文件部分代码: common.js文件 : 引用页JS文件: PS: 1.在data里初始化时间格式,是避免时间加载的第1 ...

  10. C++判断char*的指向

    char *a = "Peter"; char b[] = "Peter"; ]; strcpy_s(c, , "Peter"); 这里a指 ...