传送门

看到要求两棵树的 \(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. php 判断两个时间段是否有交集

    一开始,没啥思路,全靠百度,记录一下哈 public function demo(){ //例子 $astart = strtotime("1995-06-16 12:00:00" ...

  2. 关于 maven 打包直接运行的 fat jar (uber jar) 时需要包含本地文件系统第三方 jar 文件的问题

    关于maven打包fat jar (uber jar) 时需要包含本地文件系统第三方jar文件的问题,今天折腾了一整天.最后还是用了spring boot来做.下面是几篇关于打包的有参考价值的文章,以 ...

  3. Compile git version inside go binary

    Compile git version inside go binary Abstract 在我们编写的程序中总是希望可以直接查阅程序的版本,通过--version参数就会输出如下版本信息. Buil ...

  4. JS框架设计之对象类型判断一种子模块

    Javascript有两套数据类型,一套是基础数据类型,一套是对象数据类型.基础数据类型包括5种基本数据类型,分别是null,bool,undefined,number,string,也叫简单数据类型 ...

  5. preg_match 与 preg_match_all

    案例一: <?php $str = 'abcdef123456'; preg_match('/[a-z1-9]+/', $str, $res); var_dump($res); preg_mat ...

  6. Android多媒体技术之视频播放

    1.Android中视频播放方式 surfaceView+MediaPlayer,通过MediaPlayer来控制视频的播放.暂停.进度等: 使用VideoView 来播放,这个类其实也是继承了Sur ...

  7. 虚拟机实现finally语句块

    1.ret.jsr.jsr_w与returnAddress指令实现finally语句块 当class文件的版本号等于或高于51.0,jsr和jsr_w这两个操作码也不能出现在code数组中. 所有re ...

  8. JavaScript数据结构-17.图结构

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. manjaro 添加tash 快捷方式

    -In your home directory open /.local/share/applications/-Right click in this folder and create a emp ...

  10. AngularJS 的常用特性(二)

    3.列表.表格以及其他迭代型元素 ng-repeat可能是最有用的 Angular 指令了,它可以根据集合中的项目一次创建一组元素的多份拷贝. 比如一个学生名册系统需要从服务器上获取学生信息,目前先把 ...