[LOJ#2553][CTSC2018]暴力写挂
[LOJ#2553][CTSC2018]暴力写挂
试题描述
temporaryDO 是一个很菜的 OIer 。在 4 月,他在省队选拔赛的考场上见到了《林克卡特树》一题,其中 \(k = 0\) 的部分分是求树 \(T\) 上的最长链。可怜的 temporaryDO 并不会做这道题,他在考场上抓猫耳挠猫腮都想不出一点思路。
这时,善良的板板出现在了空中,他的身上发出璀璨却柔和的光芒,荡漾在考场上。‘‘题目并不难。’’ 板板说。那充满磁性的声音,让 temporaryDO 全身充满了力量。 他决定:写一个枚举点对求 LCA 算距离的 \(k = 0\) 的 $$O(n^2\log\ n)$ 的部分分程序!于是, temporaryDO 选择以 \(1\) 为根,建立了求 LCA 的树链剖分结构,然后写了二重 for 循环枚举点对。
然而,菜菜的 temporaryDO 不小心开小了数组,于是数组越界到了一片神秘的内存区域。但恰好的是,那片内存区域存储的区域恰好是另一棵树 \(T'\)。这样一来,程序并没有 RE ,但他求 \(x\) 和 \(y\) 的距离的时候,计算的是
\]
最后程序会输出每一对点对 \(i, j (i \le j)\) 的如上定义的‘‘距离’’ 的最大值。
temporaryDO 的程序在评测时光荣地爆零了。但他并不服气,他决定花好几天把自己的程序跑出来。请你根据 \(T\) 和 \(T'\) 帮帮可怜的 temporaryDO 求出他程序的输出。
输入
第一行包含一个整数 \(n\) ,表示树上的节点个数;
第 \(2\) 到第 \(n\) 行,每行三个整数 \(x , y , v\),表示 \(T\) 中存在一条从 \(x\) 到 \(y\) 的边,其长度为 \(v\); 第 \(n + 1\) 到第 \(2n - 1\) 行,每行三个整数 \(x , y , v\),表示 \(T'\) 中存在一条从 \(x\) 到 \(y\) 的边,其长度为 \(v\)。
输出
输出一行一个整数,表示 temporaryDO 的程序的输出。
输入示例
6
1 2 2
1 3 0
2 4 1
2 5 -7
3 6 0
1 2 -1
2 3 -1
2 5 3
2 6 -2
3 4 8
输出示例
5
数据规模及约定
对于所有数据, \(n \le 366666 , |v| \le 2017011328\)。
\(depth(p)\) 和 \(depth'(p)\) 分别表示树 \(T\)、\(T'\) 中点 \(1\) 到点 \(p\) 的距离,这里规定,距离指的是经过的边的边权总和,其中 \(depth(1) = 0\)。
\(LCA(x, y)\) 和 \(LCA'(x, y)\) 分别表示树 \(T\)、\(T'\) 中点 \(x\) 与点 \(y\) 的最近公共祖先,即在从 \(x\) 到 \(y\) 的最短路径上的距离根经过边数最少的点。
题解
搁置已久的大锅终于搞掉了……这个题让自带大常数的我卡得生活不能自理……
将第一棵树转化成二叉树后边分治,那么考虑重心边的两侧一定有一侧是更“靠近”根的;假设集合 \(A\) 离根更近,集合 \(B\) 离根更远,那么 \(\forall x \in A\) 都有 \(\forall y \in B, y' \in B, lca(x, y) = lca(x, y')\),其中 \(lca(a, b)\) 表示在原树上 \(a\) 和 \(b\) 的最近公共祖先。
那么这样我们可以枚举 \(x\),然后 \(depth(x) - depth(lca(x, y))\) 就固定了,我们就是要找到最小的 \(depth(y) - depth'(lca'(x, y))\),这个东西建一下虚树然后树形 dp 即可(正反两次 dp,一次从子树往上推,第二次从父节点更新到子节点)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
char Getchar() {
	if(Head == Tail) {
		int l = fread(buffer, 1, BufferSize, stdin);
		Tail = (Head = buffer) + l;
	}
	return *Head++;
}
int read() {
	int x = 0, f = 1; char c = Getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
	return x * f;
}
#define maxn 800010
#define maxm 1600010
#define maxlog 21
#define pii pair <int, int>
#define x first
#define y second
#define mp(x, y) make_pair(x, y)
#define LL long long
int Log[maxn<<1];
struct tree {
	int n, m, head[maxn], nxt[maxm], to[maxm], dist[maxm];
	tree(): m(0) { memset(head, 0, sizeof(head)); }
	void AddEdge(int a, int b, int c) {
		to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
		swap(a, b);
		to[++m] = b; dist[m] = c; nxt[m] = head[a]; head[a] = m;
		return ;
	}
} Tmp;
struct Tree {
	int n, m, head[maxn], nxt[maxm], to[maxm], dist[maxm], id[maxm], dep[maxn], dfn[maxn], clo, mnp[maxlog][maxn<<1];
	LL Dep[maxn];
	Tree(): m(0) { memset(head, 0, sizeof(head)); }
	void AddEdge(int a, int b, int c, int Id = 0) {
		to[++m] = b; dist[m] = c; id[m] = Id; nxt[m] = head[a]; head[a] = m;
		swap(a, b);
		to[++m] = b; dist[m] = c; id[m] = Id; nxt[m] = head[a]; head[a] = m;
		return ;
	}
	void build(int u, int fa) {
		mnp[0][dfn[u] = ++clo] = u;
		for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa) {
			dep[to[e]] = dep[u] + 1;
			Dep[to[e]] = Dep[u] + dist[e];
			build(to[e], u);
			mnp[0][++clo] = u;
		}
		return ;
	}
	void rmq_init() {
		Log[1] = 0;
		rep(i, 2, clo) Log[i] = Log[i>>1] + 1;
		rep(i, 1, Log[clo])
			rep(j, 1, clo - (1 << i) + 1) {
				int a = mnp[i-1][j], b = mnp[i-1][j+(1<<i>>1)];
				mnp[i][j] = dep[a] < dep[b] ? a : b;
			}
		return ;
	}
	int lca(int a, int b) {
		int l = dfn[a], r = dfn[b];
		if(l > r) swap(l, r);
		int t = Log[r-l+1], A = mnp[t][l], B = mnp[t][r-(1<<t)+1];
		return dep[A] < dep[B] ? A : B;
	}
} T, T1;
namespace rebuildTree {
	int sons[maxn], sonv[maxn], cs, M;
	void getT(int u, int fa) {
		for(int e = Tmp.head[u]; e; e = Tmp.nxt[e]) if(Tmp.to[e] != fa) getT(Tmp.to[e], u);
		cs = 0;
		for(int e = Tmp.head[u]; e; e = Tmp.nxt[e]) if(Tmp.to[e] != fa) sons[++cs] = Tmp.to[e], sonv[cs] = Tmp.dist[e];
		// printf("%d has %d sons\n", u, cs);
		if(cs <= 2) rep(i, 1, cs) T.AddEdge(u, sons[i], sonv[i], ++M);
		else {
			T.n++; T.AddEdge(T.n, sons[1], sonv[1], ++M); T.AddEdge(T.n, sons[2], sonv[2], ++M);
			rep(i, 3, cs - 1) T.n++, T.AddEdge(T.n, T.n - 1, 0, ++M), T.AddEdge(T.n, sons[i], sonv[i], ++M);
			T.AddEdge(u, T.n, 0, ++M); T.AddEdge(u, sons[cs], sonv[cs], ++M);
		}
		return ;
	}
	void build() {
		getT(1, 0);
		// printf("M: %d, %d\n", M, T.n);
		T.build(1, 0); T1.build(1, 0);
		T.rmq_init(); T1.rmq_init();
		return ;
	}
}
namespace Solve {
	const LL ool = 1ll << 60;
	LL ans = -ool;
	namespace Vtree {
		int KeyPoint[maxn], K;
		int m, head[maxn], nxt[maxm], to[maxm];
		LL val[maxn], extra[maxn];
		void clear() {
			rep(i, 1, K) val[KeyPoint[i]] = extra[KeyPoint[i]] = -ool, head[KeyPoint[i]] = 0;
			K = m = 0;
			return ;
		}
		void AddEdge(int a, int b) {
			to[++m] = b; nxt[m] = head[a]; head[a] = m;
			return ;
		}
		LL f[maxn], pre[maxn], suf[maxn], sonv[maxn];
		int son[maxn], cs;
		void dp(int u, int fa) {
			f[u] = val[u];
			for(int e = head[u]; e; e = nxt[e]) dp(to[e], u), f[u] = max(f[u], f[to[e]]);
			cs = 0;
			for(int e = head[u]; e; e = nxt[e]) son[++cs] = to[e], sonv[cs] = f[to[e]];
			LL nmx = -ool;
			rep(i, 1, cs) {
				pre[son[i]] = nmx;
				nmx = max(nmx, sonv[i]);
			}
			nmx = -ool;
			dwn(i, cs, 1) {
				suf[son[i]] = nmx;
				nmx = max(nmx, sonv[i]);
			}
			return ;
		}
		void dp2(int u, int fa, LL nmx) {
			if(extra[u] > -ool && (f[u] > -ool || nmx > -ool)) ans = max(ans, extra[u] + max(f[u] - T1.Dep[u], nmx));
			for(int e = head[u]; e; e = nxt[e]) {
				LL now = max(max(pre[to[e]], suf[to[e]]), val[u]);
				dp2(to[e], u, max(nmx, now > -ool ? now - T1.Dep[u] : -ool));
			}
			return ;
		}
		bool cmp(const int &a, const int &b) { return T1.dfn[a] < T1.dfn[b]; }
		void build() {
			sort(KeyPoint + 1, KeyPoint + K + 1, cmp);
			rep(i, 1, K - 1) KeyPoint[++K] = T1.lca(KeyPoint[i], KeyPoint[i+1]);
			KeyPoint[++K] = T1.lca(KeyPoint[K], KeyPoint[1]);
			sort(KeyPoint + 1, KeyPoint + K + 1, cmp);
			K = unique(KeyPoint + 1, KeyPoint + K + 1) - KeyPoint - 1;
			rep(i, 1, K - 1) {
				int a = KeyPoint[i], b = KeyPoint[i+1], c = T1.lca(a, b);
				AddEdge(c, b);
			}
			dp(KeyPoint[1], 0); dp2(KeyPoint[1], 0, -ool);
			return ;
		}
	}
	using Vtree::KeyPoint;
	using Vtree::K;
	using Vtree::val;
	using Vtree::extra;
	pii rt;
	int eid, size, best, siz[maxn];
	bool vis[maxn];
	void getrt(int u, int fa, int fae) {
		siz[u] = 1;
		for(int e = T.head[u]; e; e = T.nxt[e]) if(T.id[e] != fae && !vis[T.id[e]]) {
			int v = T.to[e];
			getrt(v, u, T.id[e]);
			siz[u] += siz[v];
		}
		if(fa && best > max(siz[u], size - siz[u])) best = max(siz[u], size - siz[u]), rt = mp(u, fa), eid = fae;
		return ;
	}
	void dfs(int u, int fae, bool tp) {
		if(u <= T1.n) {
			KeyPoint[++K] = u;
			if(tp) val[u] = T.Dep[u];
			else extra[u] = T.Dep[u] - T.Dep[T.lca(u,rt.x)];
		}
		for(int e = T.head[u]; e; e = T.nxt[e]) if(T.id[e] != fae && !vis[T.id[e]]) dfs(T.to[e], T.id[e], tp);
		return ;
	}
	void solve(pii u, int ed) {
		// printf("%d(%d) -- %d(%d)\n", u.x, siz[u.x], u.y, siz[u.y]);
		vis[ed] = 1;
		if(T.dep[u.x] > T.dep[u.y]) swap(u.x, u.y);
		dfs(u.x, 0, 0); dfs(u.y, 0, 1);
		Vtree::build();
		Vtree::clear();
		if(siz[u.x] > 1) {
			rt = mp(0, 0); eid = 0; best = (size = siz[u.x]) + 1; getrt(u.x, 0, 0);
			if(eid) solve(rt, eid);
		}
		if(siz[u.y] > 1) {
			rt = mp(0, 0); eid = 0; best = (size = siz[u.y]) + 1; getrt(u.y, 0, 0);
			if(eid) solve(rt, eid);
		}
		return ;
	}
	void main() {
		rep(i, 1, T1.n) val[i] = extra[i] = -ool;
		rt = mp(0, 0); eid = 0; best = (size = T.n) + 1; getrt(1, 0, 0);
		solve(rt, eid);
		rep(i, 1, T1.n) ans = max(ans, T.Dep[i] - T1.Dep[i]);
		return ;
	}
}
int main() {
	Tmp.n = T.n = T1.n = read();
	rep(i, 1, Tmp.n - 1) {
		int a = read(), b = read(), c = read();
		Tmp.AddEdge(a, b, c);
	}
	rep(i, 1, T1.n - 1) {
		int a = read(), b = read(), c = read();
		T1.AddEdge(a, b, c);
	} // */
	rebuildTree::build();
	Solve::main();
	printf("%lld\n", Solve::ans);
	return 0;
}
												
											[LOJ#2553][CTSC2018]暴力写挂的更多相关文章
- [CTSC2018]暴力写挂——边分树合并
		
[CTSC2018]暴力写挂 题面不错 给定两棵树,两点“距离”定义为:二者深度相加,减去两棵树上的LCA的深度(深度指到根节点的距离) 求最大的距离. 解决多棵树的问题就是降维了. 经典的做法是边分 ...
 - BZOJ5341: [Ctsc2018]暴力写挂
		
BZOJ5341: [Ctsc2018]暴力写挂 https://lydsy.com/JudgeOnline/problem.php?id=5341 分析: 学习边分治. 感觉边分治在多数情况下都能用 ...
 - BZOJ5341[Ctsc2018]暴力写挂——边分治+虚树+树形DP
		
题目链接: CSTC2018暴力写挂 题目大意:给出n个点结构不同的两棵树,边有边权(有负权边及0边),要求找到一个点对(a,b)满足dep(a)+dep(b)-dep(lca)-dep'(lca)最 ...
 - UOJ400/LOJ2553 CTSC2018 暴力写挂 边分治、虚树
		
传送门--UOJ 传送门--LOJ 跟隔壁通道是一个类型的 要求的式子中有两个LCA,不是很方便,因为事实上在这种题目中LCA一般都是枚举的对象-- 第二棵树上的LCA显然是动不了的,因为没有其他的量 ...
 - [CTSC2018]暴力写挂
		
题目描述 www.lydsy.com/JudgeOnline/upload/201805/day1(1).pdf 题解 首先来看这个我们要最大化的东西. deep[u]+deep[v]-deep[lc ...
 - bzoj 5341: [Ctsc2018]暴力写挂
		
Description Solution 边分治+边分树合并 这个题很多做法都是启发式合并的复杂度的,都有点卡 以前有个套路叫做线段树合并优化启发式合并,消掉一个 \(log\) 这个题思路类似,建出 ...
 - 并不对劲的bzoj5341:loj2553:uoj400:p4565:[Ctsc2018]暴力写挂
		
题目大意 有两棵\(n\)(\(n\leq366666\))个节点的树,\(T\)和\(T'\),有边权 \(dep(i)\)表示在\(T\)中\(i\)号点到\(1\)号点的距离,\(dep'(i) ...
 - 题解 「CTSC2018暴力写挂」
		
题目传送门 题目大意 给出两个大小为 \(n\) 的树,求出: \[\max\{\text{depth}(x)+\text{depth}(y)-\text{depth}(\text{LCA}(x,y) ...
 - Loj #2553. 「CTSC2018」暴力写挂
		
Loj #2553. 「CTSC2018」暴力写挂 题目描述 temporaryDO 是一个很菜的 OIer .在 4 月,他在省队选拔赛的考场上见到了<林克卡特树>一题,其中 \(k = ...
 
随机推荐
- 【mysql处理远程登陆授权及数据库迁移备份问题】
			
Database changedMariaDB [mysql]> grant all PRIVILEGES on mysql.* to root@'%' identified by '123'; ...
 - 一次 group by + order by 性能优化分析
			
一次 group by + order by 性能优化分析 最近通过一个日志表做排行的时候发现特别卡,最后问题得到了解决,梳理一些索引和MySQL执行过程的经验,但是最后还是有5个谜题没解开,希望大家 ...
 - 用状态机表示SFC中的并行分支
			
过去一直认为,状态机表示SFC会不会是任务复杂化,这次简单实验了一下,感觉还可以.请看下面的控制. 在SFC中,A和B是一对并行分支,汇合后转移到C分支中,怎么了用状态机表示呢?这里我们在状态机里分别 ...
 - 关于JUnit4无法支持多线程测试的解决方法
			
转自:https://segmentfault.com/a/1190000003762719 其实junit是将test作为参数传递给了TestRunner的main函数.并通过main函数进行执行. ...
 - Mysql通过Adjacency List(邻接表)存储树形结构
			
转载自:https://www.jb51.net/article/130222.htm 以下内容给大家介绍了MYSQL通过Adjacency List (邻接表)来存储树形结构的过程介绍和解决办法,并 ...
 - UVA 1593 Alignment of Code(紫书习题5-1 字符串流)
			
You are working in a team that writes Incredibly Customizable Programming Codewriter (ICPC) which is ...
 - python2.7入门---字典(Dictionary)
			
这次咱们记录的是python中的字典这个鬼,首先我们得了解,字典是另一种可变容器模型,且可存储任意类型对象.字典的每个键值 key=>value 对用冒号 : 分割,每个键值对之间用逗号 ...
 - CentOS搭建Sqoop环境
			
Sqoop是一个用来将Hadoop(Hive.HBase)和关系型数据库中的数据相互转移的工具,可以将一个关系型数据库(例如:MySQL ,Oracle ,Postgres等)中的 ...
 - RelativeSource设定绑定方向
			
<Window x:Class="Yingbao.Chapter2.RelativeEx.AppWin" xmlns="http://schemas.microso ...
 - Android开发——Android系统启动以及APK安装、启动过程
			
0. 前言 从Android手机打开开关,到我们可以使用其中的app时,这个启动过程到底是怎么样的? 1. 系统上电 当给Android系统上电,在电源接通的瞬间,CPU内的寄存器和各引脚均会被 ...