首页 > 其他分享 >【线段树合并、虚树】P5327 [ZJOI2019] 语言

【线段树合并、虚树】P5327 [ZJOI2019] 语言

时间:2023-09-15 21:46:07浏览次数:43  
标签:P5327 rt cnt fa int st dep ZJOI2019 虚树

终于 1k AC 了家人,感动吧。

贺了很久,很累。

前置题目:P3320 [SDOI2015] 寻宝游戏

虚树的边权和:

\[\sum dep_{a_x} - \sum_{x < n} dep_{a_x, a_{x + 1}} - dep_{a_{1}, a_{n}} \]

考虑转化贡献,求过该点的链的并,最后再除以二即可。

那么我们可以考虑维护以该点的子树的所有关键点以及其对子形成的虚树,那么求虚树的边权和即为所求。

然后我们考虑线段树合并维护这个问题,考虑在 \(dfn\) 上建权值线段树,然后我们每次维护左边右边的点,然后 pushup 时拼起来即可。

考虑树上差分,然后我们就可以做到塞点。注意如果要 \(O(n \log n)\) 可以使用 euler 序求 LCA。用 st 表维护区间的 \(dep\) 最小值即可。

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; i ++)
#define per(i, r, l) for (int i = r; i >= l; i --) 
using namespace std;

const int _ = 1e5 + 5, __ = _ * 20;

int n, m, dfc, cnt;
int tr[_], dfn[_], eul[_], dep[_];
int lg[_], st[_][20];
long long ans;

vector <int> e[_], ad[_], de[_];

void dfs (int x, int fa) {
	dep[x] = dep[fa] + 1,
	tr[++ dfc] = x, dfn[x] = dfc;
	eul[x] = ++ cnt, st[cnt][0] = x;
	for (int y : e[x]) 
		if (y != fa) dfs(y, x), st[++ cnt][0] = x;
}
int getLCA (int x, int y) {
	int l = eul[x], r = eul[y], k;
	if (l > r) swap(l, r);
	k = lg[r - l + 1];
	x = st[l][k], y = st[r - (1 << k) + 1][k];
	return dep[x] < dep[y] ? x : y;
}
int calc (int x, int y) {
	if (!x || !y) return 0;
	return dep[y] - dep[getLCA(x, y)];
}

int rt[_];
int tot, lc[__], rc[__], s[__], w[__], lp[__], rp[__];
void pushup (int x) {
	s[x] = s[lc[x]] + s[rc[x]] + calc(rp[lc[x]], lp[rc[x]]),
	lp[x] = lp[lc[x]] ? lp[lc[x]] : lp[rc[x]],
	rp[x] = rp[rc[x]] ? rp[rc[x]] : rp[lc[x]];
	return ;
}
void modify (int & x, int l, int r, int v, int k) {
	if (! x) x = ++ tot;
	if (l == r) {
		w[x] += k;
		if (w[x]) lp[x] = rp[x] = tr[v];
		else lp[x] = rp[x] = 0;
		return ;
	}
	int mid = (l + r) >> 1;
	v <= mid ? modify(lc[x], l, mid, v, k) : modify(rc[x], mid + 1, r, v, k);
	pushup(x); 
}
int merge (int x, int y, int l, int r) {
	if (!x || !y) return x | y;
	if (l == r) {
		w[x] += w[y];
		if (w[x]) lp[x] = rp[x] = tr[l];
		else lp[x] = rp[x] = 0;
		return x;
	}
	int mid = (l + r) >> 1;
	lc[x] = merge(lc[x], lc[y], l, mid), 
	rc[x] = merge(rc[x], rc[y], mid + 1, r);
	return pushup(x), x;
}

void dfs2 (int x, int fa) {
	for (int y : e[x]) {
		if (y == fa) continue ;
		dfs2(y, x),
		rt[x] = merge(rt[x], rt[y], 1, n); 
	}
	for (int p : ad[x]) modify(rt[x], 1, n, p, 1);
	ans += s[rt[x]] + dep[lp[rt[x]]] - dep[getLCA(lp[rt[x]], rp[rt[x]])];
//	cout << lp[rt[x]] << " " << rp[rt[x]] << endl;
	for (int p : de[x]) modify(rt[x], 1, n, p, -2);
}

int main () {
	cin >> n >> m;
	rep(i, 1, n - 1) {
		int x, y;
		scanf("%d%d", & x, & y);
		e[x].push_back(y), e[y].push_back(x);
	}
	
	dfs(1, 0);
	rep(i, 2, cnt) lg[i] = lg[i >> 1] + 1;
	for (int k = 1; (1 << k) <= cnt; k ++) 
		rep(i, 1, cnt - (1 << k) + 1) {
			int x = st[i][k - 1], y = st[i + (1 << k - 1)][k - 1];
			st[i][k] = dep[x] < dep[y] ? x : y;
		}
		
	rep(i, 1, m) {
		int x, y, lca;
		scanf("%d%d", & x, & y);
		lca = getLCA(x, y);
		ad[x].push_back(dfn[x]), ad[x].push_back(dfn[y]),
		ad[y].push_back(dfn[x]), ad[y].push_back(dfn[y]);
		de[lca].push_back(dfn[x]), de[lca].push_back(dfn[y]);
	}
	dfs2(1, 0);
	cout << ans / 2ll;
	return 0;
}

标签:P5327,rt,cnt,fa,int,st,dep,ZJOI2019,虚树
From: https://www.cnblogs.com/Custlo/p/17705963.html

相关文章

  • 【学习笔记】虚树
    点击查看目录目录定义构造虚树二次排序+LCA连边单调栈虚树,不是虚数\(i\)。定义在树形dp等题目中,树中点很少,可以直接跑dp。但是如果很大但是我们只需要查询很少的一些点呢?我们称某次询问的点为【关键点】。我们看上图,只有左边子树的两个节点被查询了,那右边的子树有......
  • 【学习笔记】(24) 虚树
    虚树常常被使用在树形dp中,当一次询问仅仅涉及到整颗树中少量结点时,为每次询问都对整棵树进行dp在时间上是不可接受的。此时,我们建立一颗仅仅包含部分关键结点的虚树,将非关键点构成的链简化成边或是剪去,在虚树上进行dp。虚树包含所有的询问点及它们之间的lca。显然虚树的叶子节点......
  • 虚树学习笔记
    观前须知:本博客中\(k\)均指关键点数量-2前置芝士你需要会基本的dfs序(下简称dfn)及性质,并且要会写LCA(推荐树剖因为快)-1典型特征关键点\(\sumk\le1e5\)之类的很小的数虚树的特点是只保存关键点及其LCA0引入例:\(\color{green}CF613D\)这个树上问题可以说非常......
  • 浅谈虚树优化线段树
    前言我们都知道动态开点权值线段树的空间复杂度是\(O(n\logV)\)的,但是很多题目中这样的空间是会被卡的,那我们能不能优化呢?实现看看下面这一棵树:在上图中,红色节点使我们平常会开的点。但是我们发现,其实只要维护绿色的点和红色的叶子结点。其实,绿色节点就是所有叶子结点......
  • 虚树 学习笔记
    模板题题目传送门给定一棵树,每次给出\(k\)个点,断掉一些边,然后让这些给出的点和\(1\)号点不连通,求断边的边权和的最小值。数据组数\(T\le5\cdot10^5\),树的点数\(n\le2.5\cdot10^5\),\(\sumk\le5\cdot10^5\)题目解析我们发现每次给出的是一部分点,所以我们只需要......
  • 虚树
    我们用单调栈来建虚树,如果求lca的复杂度为\(O(\logn)\)的话,整个求虚树的复杂度应该为\(O(n\logn)\)。整体思路为我们用首先把所有关键点按照dfs序排序,然后首先把\(1\)节点加入栈。整个过程我们维护一条左链。我们考虑往里面加入一个新的关键点。求一下这个点和栈顶的......
  • 虚树 学习笔记
    虚树学习笔记引入我们在解决树上问题时,往往都是对整棵树进行处理,或者每次询问都对一个点、点对进行处理,这类题型一般都可以通过dp、树剖解决;然而,有一类问题要求我们每次对树上一些关键点进行处理。这类问题的特点就是询问次数多,而询问的点的总数不多。可如果我们每次都把整棵......
  • 虚树
    有一类问题,整棵树的节点数非常多,但是询问只和其中的若干个关键节点(以及必要的维护他们之间的连通性的节点)有关,这类问题我们可以采用虚树来优化。一般采用单调栈构建虚树,首先将所有关键节点按\(dfn\)排序,把\(1\)号节点入栈。单调栈中维护的一直是一条链,我们扫一遍所有的节点,......
  • 虚树学习笔记
    概念虚树是一棵树,相对于原树而言。它删去原树上某些点,再按原树父子关系连边构成的树。它对树上算法有一定优化。假如一个树上问题仅与部分节点有关,如树形DP,DP值仅在部分节点有改变,那么就可以已这部分节点建成虚树,省略其他部分,复杂度为部分节点总和。例:消耗战本题的删边DP只与......
  • [ZJOI2019]麻将
    dp套dp经典例题。这种题一般都是给你一个奇怪的合法条件,然后去做一些计数之类的东西,直接设计状态很不好做。我们考虑先设计一个判定合法的dp,以这个dp的状态和结果作为状态去dp。更一般的,我们发现dp的过程有初始状态和终止状态,转移看成有向边,可以建出一个自动机来。dp......