传送门

看到要求两棵树的 \(lca\) 深度不太好操作

考虑枚举第二棵树的 \(lca\),这样剩下的都是只和第一棵树有关的

而注意到 \(dis(x,y)=d(x)+d(y)-2d(lca(x,y))\)

那么 \(d(x)+d(y)-d(lca(x,y))=\frac{1}{2}(dis(x,y)+d(x)+d(y))\)

这样就好多了,可以直接沿用WC2018通道的做法

对于第一棵树进行边分治,把两边的集合合起来在第二棵树上建个虚树

然后在虚树上枚举 \(lca\),\(dp\) 统计不属于同一个集合的答案

复杂度 \(\Theta(nlog^2n)\) 可以强行优化到 \(nlogn\)

上述做法不够优美

考虑另外一种做法,仍然是对于第一棵树进行边分治

对于每个点,维护每一次边分治时的集合和到分治中心的距离

那么只需要在第二棵树上枚举 \(lca\) 统计每次边分治时不同集合的最大的贡献就好了

这个可以用线段树(二叉树)合并实现,\(nlogn\)

然后考虑怎么维护每一次边分治时的集合和到分治中心的距离

直接想法是直接暴力插入,\(nlog^2n\)

然而边分治每次是分成两个集合,类似一个线段树的结构

所以可以对于每个点维护一个线段树(二叉树),要保证树上每个点和是哪一次边分治对应

只需要将这些点的树的当前点记录下来

每次边分治的第一个集合向左跳,第二个向右跳就可以 \(nlogn\) 建树了

复杂度 \(\Theta(nlogn)\)

# include <bits/stdc++.h>
using namespace std;
typedef long long ll; namespace IO {
const int maxn(1 << 21 | 1); char ibuf[maxn], *iS, *iT, c;
int f; inline char Getc() {
return iS == iT ? (iT = (iS = ibuf) + fread(ibuf, 1, maxn, stdin), (iS == iT ? EOF : *iS++)) : *iS++;
} template <class Int> inline void In(Int &x) {
for (c = Getc(), f = 1; c < '0' || c > '9'; c = Getc()) f = c == '-' ? -1 : 1;
for (x = 0; c >= '0' && c <= '9'; c = Getc()) x = (x << 1) + (x << 3) + (c ^ 48);
x *= f;
}
} using IO :: In; const int maxn(4e5 + 5);
const ll inf(1e18); int first1[maxn << 1], first2[maxn], cnt, head[maxn], m, n, fw[maxn << 1], tmp[maxn << 1], len;
int size[maxn << 1], mn, rte, vis[maxn << 2], sz;
int rt[maxn], tot, ls[maxn * 40], rs[maxn * 40], fa[maxn], fc[maxn];
ll dis[maxn], ans, lmx[maxn * 40], rmx[maxn * 40]; struct Edge {
int to, next, w;
} edg[maxn << 1], edge1[maxn << 2], edge2[maxn << 1]; inline void Addpre(int u, int v, int w) {
edg[cnt] = (Edge){v, head[u], w}, head[u] = cnt++;
edg[cnt] = (Edge){u, head[v], w}, head[v] = cnt++;
} inline void Add1(int u, int v, int w) {
edge1[cnt] = (Edge){v, first1[u], w}, first1[u] = cnt++;
edge1[cnt] = (Edge){u, first1[v], w}, first1[v] = cnt++;
} inline void Add2(int u, int v, int w) {
edge2[cnt] = (Edge){v, first2[u], w}, first2[u] = cnt++;
edge2[cnt] = (Edge){u, first2[v], w}, first2[v] = cnt++;
} int Build(int l, int r) {
if (l > r) return 0;
if (l == r) return tmp[l];
int cur, ls, rs, mid;
cur = ++m, mid = (l + r) >> 1;
ls = Build(l, mid), rs = Build(mid + 1, r);
if (ls) Add1(cur, ls, fw[ls]);
if (rs) Add1(cur, rs, fw[rs]);
return cur;
} void Rebuild(int u, int ff) {
int e, v, ls, rs, mid;
len = 0;
for (e = head[u]; ~e; e = edg[e].next)
if ((v = edg[e].to) != ff) fw[v] = edg[e].w, tmp[++len] = v;
mid = (len + 1) >> 1;
ls = Build(1, mid), rs = Build(mid + 1, len);
if (ls) Add1(u, ls, fw[ls]);
if (rs) Add1(u, rs, fw[rs]);
for (e = head[u]; ~e; e = edg[e].next)
if ((v = edg[e].to) != ff) dis[v] = dis[u] + fw[v], Rebuild(v, u);
} void Getroot(int u, int ff, int fe) {
int e, v;
size[u] = 1;
for (e = first1[u]; ~e; e = edge1[e].next)
if ((v = edge1[e].to) != ff && !vis[e]) {
Getroot(v, u, e);
size[u] += size[v];
}
if (abs(sz - size[u] * 2) < mn) mn = abs(sz - size[u] * 2), rte = fe;
} void Dfs1(int u, int ff, int type, ll d) {
if (u <= n) {
++tot, lmx[tot] = rmx[tot] = -inf;
if (fa[u]) fc[u] ? rs[fa[u]] = tot : ls[fa[u]] = tot;
else rt[u] = tot;
fa[u] = tot, fc[u] = type;
type ? rmx[tot] = d + dis[u] : lmx[tot] = d + dis[u];
}
int e, v;
for (e = first1[u]; ~e; e = edge1[e].next)
if ((v = edge1[e].to) != ff && !vis[e]) Dfs1(v, u, type, d + edge1[e].w);
} void Solve(int nrt) {
if (nrt == -1 || vis[nrt]) return;
int rt1, rt2, tmp1, tmp2;
rt1 = edge1[nrt].to, rt2 = edge1[nrt ^ 1].to;
vis[nrt] = vis[nrt ^ 1] = 1;
tmp1 = size[rt1] > size[rt2] ? sz - size[rt2] : size[rt1];
tmp2 = size[rt2] > size[rt1] ? sz - size[rt1] : size[rt2];
Dfs1(rt1, rt2, 0, edge1[nrt].w), Dfs1(rt2, rt1, 1, 0);
sz = tmp1, mn = m + 1, Getroot(rt1, rt2, -1), Solve(rte);
sz = tmp2, mn = m + 1, Getroot(rt2, rt1, -1), Solve(rte);
} int Merge(int x, int y, ll del) {
if (!x || !y) return x | y;
ans = max(ans, lmx[x] + rmx[y] - del);
ans = max(ans, lmx[y] + rmx[x] - del);
lmx[x] = max(lmx[x], lmx[y]), rmx[x] = max(rmx[x], rmx[y]);
ls[x] = Merge(ls[x], ls[y], del), rs[x] = Merge(rs[x], rs[y], del);
return x;
} void Dfs2(int u, int ff, ll d) {
int e, v;
ans = max(ans, dis[u] * 2 - d * 2);
for (e = first2[u]; ~e; e = edge2[e].next)
if ((v = edge2[e].to) != ff) {
Dfs2(v, u, d + edge2[e].w);
rt[u] = Merge(rt[u], rt[v], d + d);
}
} int main() {
int i, u, v, w;
memset(head, -1, sizeof(head));
memset(first1, -1, sizeof(first1));
memset(first2, -1, sizeof(first2));
ans = -inf, In(n), m = n;
for (i = 1; i < n; ++i) In(u), In(v), In(w), Addpre(u, v, w);
cnt = 0;
for (i = 1; i < n; ++i) In(u), In(v), In(w), Add2(u, v, w);
cnt = 0, Rebuild(1, 0);
sz = m, mn = m + 1, Getroot(1, 0, -1);
Solve(rte), Dfs2(1, 0, 0);
printf("%lld\n", ans >> 1);
return 0;
}

UOJ#400. 【CTSC2018】暴力写挂的更多相关文章

  1. 【UOJ#400】暴力写挂

    题目链接 题意 两棵树 , 求出下面式子的最大值. \[dep[u]+dep[v]-dep[LCA(u,v)]-dep'[LCA'(u,v)]\] Sol 边分治. 与第一棵树有关的信息比较多,所以对 ...

  2. [CTSC2018]暴力写挂——边分树合并

    [CTSC2018]暴力写挂 题面不错 给定两棵树,两点“距离”定义为:二者深度相加,减去两棵树上的LCA的深度(深度指到根节点的距离) 求最大的距离. 解决多棵树的问题就是降维了. 经典的做法是边分 ...

  3. [LOJ#2553][CTSC2018]暴力写挂

    [LOJ#2553][CTSC2018]暴力写挂 试题描述 temporaryDO 是一个很菜的 OIer .在 4 月,他在省队选拔赛的考场上见到了<林克卡特树>一题,其中 \(k = ...

  4. BZOJ5341: [Ctsc2018]暴力写挂

    BZOJ5341: [Ctsc2018]暴力写挂 https://lydsy.com/JudgeOnline/problem.php?id=5341 分析: 学习边分治. 感觉边分治在多数情况下都能用 ...

  5. BZOJ5341[Ctsc2018]暴力写挂——边分治+虚树+树形DP

    题目链接: CSTC2018暴力写挂 题目大意:给出n个点结构不同的两棵树,边有边权(有负权边及0边),要求找到一个点对(a,b)满足dep(a)+dep(b)-dep(lca)-dep'(lca)最 ...

  6. UOJ400/LOJ2553 CTSC2018 暴力写挂 边分治、虚树

    传送门--UOJ 传送门--LOJ 跟隔壁通道是一个类型的 要求的式子中有两个LCA,不是很方便,因为事实上在这种题目中LCA一般都是枚举的对象-- 第二棵树上的LCA显然是动不了的,因为没有其他的量 ...

  7. [CTSC2018]暴力写挂

    题目描述 www.lydsy.com/JudgeOnline/upload/201805/day1(1).pdf 题解 首先来看这个我们要最大化的东西. deep[u]+deep[v]-deep[lc ...

  8. bzoj 5341: [Ctsc2018]暴力写挂

    Description Solution 边分治+边分树合并 这个题很多做法都是启发式合并的复杂度的,都有点卡 以前有个套路叫做线段树合并优化启发式合并,消掉一个 \(log\) 这个题思路类似,建出 ...

  9. 并不对劲的bzoj5341:loj2553:uoj400:p4565:[Ctsc2018]暴力写挂

    题目大意 有两棵\(n\)(\(n\leq366666\))个节点的树,\(T\)和\(T'\),有边权 \(dep(i)\)表示在\(T\)中\(i\)号点到\(1\)号点的距离,\(dep'(i) ...

  10. 题解 「CTSC2018暴力写挂」

    题目传送门 题目大意 给出两个大小为 \(n\) 的树,求出: \[\max\{\text{depth}(x)+\text{depth}(y)-\text{depth}(\text{LCA}(x,y) ...

随机推荐

  1. Java多线程——线程封闭

    线程封闭:当访问共享的可变数据时,通常需要同步.一种避免同步的方式就是不共享数据.如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭(thread  confinement) 线程封闭技术一 ...

  2. uml地址栏参数特殊字符处理

    转义方法: function URLencode(sStr) { return escape(sStr).replace(/\+/g, '%2B').replace(/\"/g,'%22') ...

  3. css的reset和常用的html标签的默认样式整理

    先看下常用的这些标签浏览器的默认样式有哪些: body{ margin: 8px;} hr{ border:1px inset; margin-top:.5em;margin-bottom:.5em; ...

  4. [转] Java中Comparator进行对象排序

    [From] https://blog.51cto.com/thinklili/2063244 Java在8后引入了lambda表达式和流,使得排序方法有了变化 class User { int id ...

  5. Linux - iptables firewalld

    目录 iptables firewalld iptables 1.iptables 的基本使用 启动: service start iptabls 关闭: service stopiptabls 查看 ...

  6. 关于window.onload和body onload冲突的解决办法

    在学习用js在 页面中动态显示当前时间 和依次读取公告栏信息的 实验中 发现在将两个页面整合时 window.onload=function (){}和 <body onload="d ...

  7. pull强制覆盖本地

    今天我总结的是在项目中经常用到的Git命令,上传和下拉文件. 当然在进行上传和下拉操作之前,你首先要做的就是将本地和Git库连接起来. 连接命令: git remote add origin + 你G ...

  8. 2 小时学会 Spring Boot

    一. 什么是 Spring Boot Takes an opinionated view of building production-ready Spring applications. Sprin ...

  9. windows 7 做AP

    启动脚本: @echo off netsh wlan set hostednetwork mode=allow ssid=<ap-name> key=<password> ne ...

  10. PTA (Advanced Level) 1014 Waiting in Line

    Waiting in Line Suppose a bank has N windows open for service. There is a yellow line in front of th ...