# CF1192B 题解
题目链接
跟着 duyidalao 的思路,轻而易举 (qian nan wan xian) 调出了本题。
既然 @ duyi 没放代码,那我就来一发吧。
# 题意简述
有一个 个点的带权无向树, 次操作,每次修改一条边的权值,要求在每次修改后,输出树的直径大小,强制在线。
# 题目分析
-
对于两棵树合并后新树的直径,其两端点一定出自两树直径四个端点中,根据这一条性质我们就可以用线段树维护原树,自下向上合并节点就相当于合并它所代表的两棵子树。
-
每次合并的时候,需要我们计算树上两点间距离,记该节点到根的距离为 , 则 节点 到 的距离为 。在四个点的六组搭配中,选择直径最大的一组作为答案向上传递。
-
求树上到根的距离利用线段树 (这时候是区别于第一条的另一棵) 用两种选择,将边权化为点权后,一是单点修改加上区间查询,而是区间修改加上单点查询 (边权改变影响的只有子树,而子树在树剖后节点编号连续)。而这里我们采取后者。至于为什么不用前者...
血的教训。
# 思路实现
- 我们对求出原树的 dfs 序,同时记录以该节点为根的子树在 dfs 序中的开始和结束编号,记为 和 , 对 dfs 序建立第一棵线段树。 该线段树维护树的直径。
- 对原树重链剖分,建第二棵线段树维护每个点到根的路径长。当然还要求 LCA 。
- 可以使用 unordered_map 记录下求过的 LCA, 用 map 会多一个 的复杂度。
# Code
代码中函数名带有 '_1' 的为维护直径的线段树,带有 '_2' 的为维护到根距离的线段树。
树剖就不需要解释了吧 (
auto dfs1(int x, int fa) -> void | |
{ | |
f[x] = fa; siz[x] = 1; | |
dep[x] = dep[fa]+1; | |
int maxson = 0, y; | |
for(int i = head[x]; i; i = a[i].next) | |
{ | |
y = a[i].to; if(y == fa) continue; | |
dfs1(y, x); siz[x] += siz[y]; | |
if(siz[y] > siz[maxson]) maxson = y; | |
} | |
son[x] = maxson; | |
} | |
auto dfs2(int x, int tp) -> void | |
{ | |
id[x] = ++tim; top[x] = tp; | |
if(son[x]) | |
{ | |
dfs2(son[x], tp); int y; | |
for(int i = head[x]; i; i = a[i].next) | |
{ | |
y = a[i].to; | |
if(y == f[x] or y == son[x]) continue; | |
dfs2(y, y); | |
} | |
} | |
} | |
auto LCA(int x, int y) -> int | |
{ | |
while(top[x] != top[y]) | |
{ | |
if(dep[top[x]] < dep[top[y]]) y = f[top[y]]; | |
else x = f[top[x]]; | |
} | |
if(id[x] > id[y]) return y; | |
return x; | |
} |
求 dfs 序
auto dfsx(int x) -> void | |
{ | |
d[++ct] = x; //dfs 序数组 | |
beg[x] = ct; // 子树起点 | |
for(int i = head[x], y; i; i = a[i].next) | |
{ | |
y = a[i].to; if(y == f[x]) continue; | |
dfsx(y); | |
} | |
::end[x] = ct; // 子树终点 (变量名 end 有冲突,所以加了作用域) | |
} |
第一棵线段树
auto build_1(int l, int r, int i) -> void | |
{ | |
dr[i].l = l; | |
dr[i].r = r; | |
if(l == r) return (void)(dr[i].u = dr[i].v = d[l]); | |
int mid = (l+r)>>1; | |
build_1(l, mid, ls(i)); | |
build_1(mid+1, r, rs(i)); | |
pushup_1(i); | |
} | |
auto change_1(int l, int r, int i) -> void | |
{ | |
if(dr[i].l > r or dr[i].r < l) return ; | |
if(dr[i].l >= l and dr[i].r <= r) return ; | |
change_1(l, r, ls(i)); | |
change_1(l, r, rs(i)); | |
pushup_1(i); | |
} | |
auto pushup_1(int i) -> void | |
{ | |
lu = dr[ls(i)].u, lv = dr[ls(i)].v; | |
ru = dr[rs(i)].u, rv = dr[rs(i)].v; | |
/* 左右子树的较优解 */ | |
if(dr[ls(i)].val >= dr[rs(i)].val) | |
dr[i].u = dr[ls(i)].u, | |
dr[i].v = dr[ls(i)].v, | |
dr[i].val = dr[ls(i)].val; | |
else | |
dr[i].u = dr[rs(i)].u, | |
dr[i].v = dr[rs(i)].v, | |
dr[i].val = dr[rs(i)].val; | |
/* 枚举剩下四种情况 */ | |
U = lu, V = ru; | |
len = calc(U, V); | |
if(ru != rv) // 重复的不计算了 | |
{ | |
len2 = calc(lu, rv); | |
if(len2 > len) U = lu, V = rv, len = len2; | |
} | |
if(lu != lv) // 重复的不计算了 | |
{ | |
len2 = calc(lv, ru); | |
if(len2 > len) U = lv, V = ru, len = len2; | |
if(ru != rv) // 重复的不计算了 | |
{ | |
len2 = calc(lv, rv); | |
if(len2 > len) U = lv, V = rv, len = len2; | |
} | |
} | |
if(len > dr[i].val) | |
dr[i].val = len, dr[i].u = U, dr[i].v = V; | |
} | |
auto calc(int x, int y) -> long | |
{ | |
int lca; | |
/* 将 x,y 压入一个数,就不用 pair 了 */ | |
if( !mp.count(((long)(x)<<32ll)+y) ) mp[((long)(x)<<32ll)+y] = lca = LCA(x, y); | |
else lca = mp[((long)(x)<<32ll)+y]; | |
return query_2(id[x], 1)+query_2(id[y], 1)-2*query_2(id[lca], 1); | |
} |
第二棵线段树
auto build_2(int l, int r, int i) -> void | |
{ | |
tr[i].l = l; | |
tr[i].r = r; | |
if(l == r) return ; | |
int mid = (l+r)>>1; | |
build_2(l, mid, ls(i)); | |
build_2(mid+1, r, rs(i)); | |
} | |
auto add_2(int l, int r, long k, int i) -> void | |
{ | |
if(tr[i].l > r or tr[i].r < l) return ; | |
if(tr[i].l >= l and tr[i].r <= r) | |
{ | |
tr[i].lz += k; | |
tr[i].val += k*(tr[i].r-tr[i].l+1); | |
return ; | |
} | |
pushdown_2(i); | |
add_2(l, r, k, ls(i)); | |
add_2(l, r, k, rs(i)); | |
tr[i].val = tr[ls(i)].val+tr[rs(i)].val; | |
} | |
auto query_2(int x, int i) -> long | |
{ | |
if(tr[i].l > x or tr[i].r < x) return 0; | |
if(tr[i].l == x and tr[i].r == x) return tr[i].val; | |
pushdown_2(i); | |
return query_2(x, ls(i))+query_2(x, rs(i)); | |
} | |
auto pushdown_2(int i) -> void | |
{ | |
if(!tr[i].lz) return ; | |
tr[ls(i)].lz += tr[i].lz; | |
tr[rs(i)].lz += tr[i].lz; | |
tr[ls(i)].val += tr[i].lz*(tr[ls(i)].r-tr[ls(i)].l+1); | |
tr[rs(i)].val += tr[i].lz*(tr[rs(i)].r-tr[rs(i)].l+1); | |
tr[i].lz = 0; | |
} |
接下来是主函数,得斯
auto main() -> signed | |
{ | |
scanf("%d%d%lld", &n, &q, &w); | |
for(int i = 1; i < n; i += 1) | |
{ | |
scanf("%d%d%lld", &u[i], &v[i], &c[i]); | |
add_edge(u[i], v[i]); | |
add_edge(v[i], u[i]); | |
} | |
dfs1(1, 0); dfs2(1, 1); // 剖了它 | |
build_2(1, n, 1); //_____ | |
for(int i = 1; i < n; i += 1) | |
{ | |
if(dep[u[i]] > dep[v[i]]) // 加入初始边权 | |
add_2(id[u[i]], id[u[i]]+siz[u[i]]-1, c[i], 1); | |
else | |
add_2(id[v[i]], id[v[i]]+siz[v[i]]-1, c[i], 1); | |
} | |
dfsx(1); | |
build_1(1, n, 1); | |
for(int i = 1, D; i <= q; i += 1) | |
{ | |
scanf("%d%lld", &D,&E); | |
D = (D+lst)%(n-1)+1; | |
E = (E+lst)%w; | |
if(dep[u[D]] > dep[v[D]]) | |
{ | |
add_2(id[u[D]], id[u[D]]+siz[u[D]]-1, E-c[D], 1); // 权值的修改 | |
change_1(beg[u[D]], ::end[u[D]], 1); // 直径的更新 | |
c[D] = E; | |
} | |
else | |
{ | |
add_2(id[v[D]], id[v[D]]+siz[v[D]]-1, E-c[D], 1); | |
change_1(beg[v[D]], ::end[v[D]], 1); | |
c[D] = E; | |
} | |
lst = dr[1].val; | |
printf("%lld\n", lst); | |
} | |
return 0; | |
} |
# 结语
常数有那么亿点点大。
若有什么问题欢迎评论或私信指出。