首页 > 其他分享 >长链剖分

长链剖分

时间:2024-02-19 22:33:25浏览次数:26  
标签:长链 剖分 int len son now dp first

谁家刚学会一个知识点就放黑来做啊。


长链剖分

建议在会重链剖分的基础上再来学长链剖分。

长链剖分与重链剖分不同的地方在于:重剖以子树大小来选重儿子,长剖以子树深度来选重儿子。

长剖的优势有: 可以 \(\mathcal{O}(1)\) 求 \(k\) 级祖先,可以在与深度有关的树形 dp 中优化掉一维。

长剖的劣势有:求 LCA 是 \(\mathcal{O}(\sqrt{n})\) 的(其实用二分 + \(k\) 级祖先就好)。

代码就从重剖那里改改就好。

长链剖分优化 dp

CF1009F Dominant Indices

先来一道例题:CF1009F

题意:

给定一棵以 \(1\) 为根,\(n\) 个节点的树。设 \(d(u,x)\) 为 \(u\) 子树中到 \(u\) 距离为 \(x\) 的节点数。
对于每个点,求一个最小的 \(k\),使得 \(d(u,k)\) 最大。

有一个很明显的“dp”:令 \(dp_{i, j}\) 表示以 \(i\) 为根的子树内深度为 \(j\) 的结点个数。

转移方程:\(dp_{u, i} = \sum\limits_{v \in son_{u}}dp_{v, i - 1}(i \geqslant 1)\),\(dp_{u, 0} = 1\)。

(这里就是一个简单的统计所以连 dp 都算不上。)

但是这么做是 \(\mathcal{O}(n^2)\) 的,怎么优化呢?

用一种类似 dsu on tree 的思想,先计算某个子树的 dp,再用一种特殊的方法快速转移(因为第一个被计算的子树一般都有特殊转移方法),再暴力合并其它子树。

而这个时候我们如果选择深度最大的那棵子树先合并,那么就可以将时间复杂度的次数将为 \(1\)!

为什么呢?因为在每个结点的第一次转移中,有:\(dp_{u, i} = dp_{v, i - 1}\),我们完全可以把相同的那一部分给两个结点共用,多出来的一个点也就是 \(dp_{u, 0}\) 设为 \(0\) 就好。

而其它的结点,每条链仅仅会在链顶的时候被暴力合并,而合并的复杂度是 \(\mathcal{O}(len)\)(\(len\) 为链长),均摊下来就是 \(\mathcal{O}(n)\)。

有点小抽象,而且这个复杂度降得也给我整得不明不白的,但这么分析下来它就是降了。

放一份代码:

#include <bits/stdc++.h>
using namespace std;
int n, u, v, son[1000005], len[1000005], ans[1000005], *dp[1000005];
vector<int> g[1000005];
void dfs1(int now, int father) {
	for(const auto& i : g[now]) {
		if(i != father) {
			dfs1(i, now);
			if(len[i] > len[son[now]]) son[now] = i;
		}
	}
	len[now] = len[son[now]] + 1;
}
void dfs2(int now, int father) {
	if(now != son[father]) dp[now] = new int[len[now]];
	else dp[now] = dp[father] + 1;
	dp[now][0] = 1;
	if(son[now]) {
		dfs2(son[now], now);
		ans[now] = ans[son[now]] + 1;
	}
	for(const auto& i : g[now]) {
		if(i != father && i != son[now]) {
			dfs2(i, now);
			for(int j = 0; j < len[i]; ++j) {
				dp[now][j + 1] += dp[i][j];
				if(dp[now][j + 1] > dp[now][ans[now]] || (dp[now][j + 1] == dp[now][ans[now]] && j + 1 < ans[now])) {
					ans[now] = j + 1;
				}
			}
		}
	}
	if(dp[now][ans[now]] == 1) ans[now] = 0;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 2; i <= n; ++i) {
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs1(1, 1);
	dfs2(1, 1);
	for(int i = 1; i <= n; ++i) cout << ans[i] << '\n';
	return 0;
}

WC2010 重建计划

首先平均数的部分用 01 分数规划的思想用二分把它转化成检查 \(\sum v_{i} \geqslant 0\)。

也就是说我们要在树上找到一条链使得这条链的权值和非负。

如何统计链?当然是在两端的 LCA 处统计,所以我们只要知道一个点连下去的链的最大权值和即可。

令 \(dp_{i, j}\) 表示以 \(i\) 为链顶,长度为 \(j\) 的链的最大权值和,转移有:

\[dp_{u, i} = \max\limits_{v \in son_{u}}\left\{dp_{v, i - 1} + w(u, v)\right\} \]

类似例题,先特殊转移长链剖分出来的重儿子,有 \(dp_{u, i} = dp_{v, i - 1} + w(u, v)\)。

如何判断是否存在一条链的权值和非负?在转移的时候以及转移结束后判断即可。

具体地,枚举暴力转移的链的长度的时候(假设长度为 \(len\)),在已经转移好的部分中查询是否有一条长度在 \([\max(L - len, 0), U - len]\) 的链与其的权值和非负。

也就是说我们需要实现「区间加」,「单点修」,「区间查询 \(\max\)」,「单点查询」这几个功能,用一棵线段树即可。

其实如果模板题会了的话这道题还是很简单的。

代码:

#include <bits/stdc++.h>
using namespace std;
constexpr double eps = 1e-4;
int n, lower, upper, u, v, w, cnt, son[100005], val[100005], len[100005], lt[100005], rt[100005];
double sub, bl, br, mid, ans;
vector< pair<int, int> > g[100005];
bool flag;
struct Segment_Tree {
	int l[400005], r[400005];
	double mx[400005], tag[400005];
	#define lc (k << 1)
	#define rc (lc | 1)
	#define mid ((l[k] + r[k]) >> 1)
	void build(int k) {
		mx[k] = -1e15, tag[k] = 0;
		if(l[k] == r[k]) return;
		l[lc] = l[k], r[lc] = mid, l[rc] = mid + 1, r[rc] = r[k];
		build(lc), build(rc);
	}
	void push_down(int k) {
		mx[lc] += tag[k], mx[rc] += tag[k];
		tag[lc] += tag[k], tag[rc] += tag[k];
		tag[k] = 0;
	}
	void push_up(int k) {
		mx[k] = max(mx[lc], mx[rc]);
	}
	void change_add(int k, const int& L, const int& R, const double& v) {
		if(L <= l[k] && r[k] <= R) {
			tag[k] += v;
			mx[k] += v;
			return;
		}
		push_down(k);
		if(L <= mid) change_add(lc, L, R, v);
		if(R > mid) change_add(rc, L, R, v);
		push_up(k);
	}
	void change_max(int k, const int& pos, const double& v) {
		if(l[k] == r[k]) {
			mx[k] = max(mx[k], v);
			return;
		}
		push_down(k);
		if(pos <= mid) change_max(lc, pos, v);
		else change_max(rc, pos, v);
		push_up(k);
	}
	double ask(int k, const int& L, const int& R) {
		if(L > R) return -1e15;
		if(L <= l[k] && r[k] <= R) return mx[k];
		double ret = -1e15;
		push_down(k);
		if(L <= mid) ret = max(ret, ask(lc, L, R));
		if(R > mid) ret = max(ret, ask(rc, L, R));
		push_up(k);
		return ret;
	}
	#undef mid
} tree;
void dfs1(int now, int father) {
	for(const auto& i : g[now]) {
		if(i.first != father) {
			dfs1(i.first, now);
			if(len[i.first] > len[son[now]]) son[now] = i.first;
		}
	}
	len[now] = len[son[now]] + 1;
}
void dfs2(int now, int father) {
	if(now != son[father]) {
		lt[now] = cnt;
		rt[now] = cnt + len[now] - 1;
		cnt += len[now];
	}
	else lt[now] = lt[father] + 1, rt[now] = rt[father];
	for(const auto& i : g[now]) {
		if(i.first != father) {
			if(i.first == son[now]) val[now] = i.second;
			dfs2(i.first, now);
		}
	}
}
void dfs3(int now, int father) {
	if(son[now]) {
		dfs3(son[now], now);
		tree.change_add(1, lt[now] + 1, rt[now], val[now] - sub);
	}
	tree.change_max(1, lt[now], 0.0);
	for(const auto& i : g[now]) {
		if(i.first != father && i.first != son[now]) {
			dfs3(i.first, now);
			for(int j = 0; j < len[i.first]; ++j) {
				auto dp = tree.ask(1, lt[i.first] + j, lt[i.first] + j) + i.second - sub;
				if(tree.ask(1, lt[now] + max(lower - j - 1, 0), min(lt[now] + upper - j - 1, rt[now])) + dp >= 0.0) {
					flag = true;
				}
			}
			for(int j = 0; j < len[i.first]; ++j) {
				auto dp = tree.ask(1, lt[i.first] + j, lt[i.first] + j) + i.second - sub;
				tree.change_max(1, lt[now] + j + 1, dp);
			}
		}
	}
	if(tree.ask(1, lt[now] + lower, min(lt[now] + upper, rt[now])) >= 0.0) flag = true;
}
bool check(double c) {
	sub = c, flag = false;
	tree.build(1);
	dfs3(1, 1);
	return flag;
}
int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> lower >> upper;
	for(int i = 2; i <= n; ++i) {
		cin >> u >> v >> w;
		g[u].push_back(make_pair(v, w));
		g[v].push_back(make_pair(u, w));
	}
	dfs1(1, 1);
	cnt = 1;
	dfs2(1, 1);
	tree.l[1] = 1, tree.r[1] = n;
	check(2.5);
	bl = 1.0, br = 1000000.0;
	while(br - bl > eps) mid = (bl + br) / 2.0, check(mid) ? (ans = mid, bl = mid + eps) : (br = mid - eps);
	cout << fixed << setprecision(3) << ans;
	return 0;
}

标签:长链,剖分,int,len,son,now,dp,first
From: https://www.cnblogs.com/A-box-of-yogurt/p/18022089

相关文章

  • P5478 [BJOI2015] 骑士的旅行 - 重链剖分
    首先分析一下题目,对于这棵树,操作如下:查询从X到Y的路径上的前k大的值。把$P_i$上的武力值减去一个$F_i$并在Y上的武力值加上一个$F_i$,再把$P_i$改成Y。将$P_i$上的武力值减去一个$F_i$再加上一个Y,并把$F_i$改成Y。由第一个我们可以想到,先用树剖,再用......
  • P3384 【模板】重链剖分/树链剖分 - SGT && 重链剖分
    本题是非常非常非常纯粹的树剖,利用了重链剖分后下标的性质不多说上代码就好了#include<cstdio>#include<vector>#definelllonglongusingnamespacestd;constintN=1e5+5;intn,m,r;llp,a[N],b[N];vector<int>V[N];intfa[N],dep[N],siz[N],hson[N]......
  • P3038 [USACO11DEC] Grass Planting G - 重链剖分
    本题可以说是板题P3384的弱化版,只不过要改的变成了边权边权很好处理,只需要将每个边的边权下放到两端点深度比较深的上面就好了(因为每个点连比它浅的节点必定只有一条边)。那么就将模板改一下就好了代码如下:#include<cstdio>usingnamespacestd;constintN=1e5+5;in......
  • 树链剖分
    树链剖分分为重链剖分和长链剖分重链剖分首先是重链剖分,树链剖分顾名思义是一种将代码大小增加1k树剖成一条条链的方法。定义:重儿子该节点所有儿子中以该儿子为根的子树最大的儿子重边连接该节点与该节点的重儿子的那条边重链由根节点或一个轻儿子为链头一路重边连到叶......
  • 树链剖分
    看逆天算法,品百味OI#41.重链剖分给出如下定义:重子结点:一个点的子节点中子树最大的点轻子节点:剩余的子节点重边:该节点到重子节点的边轻边:到轻子节点的边重链:若干条首尾相接的重边预处理由两个DFS实现DFS1:记录节点的父节点、深度、子树大小、重子结点DFS......
  • 重链剖分
    重链剖分优先走重儿子,路径跳不超过\(O(\logn)\)intsiz[N],fa[N],dep[N],top[N],dfn[N],hson[N],dfc;//注意每个都要处理voiddfs1(intx,intFa){ fa[x]=Fa; siz[x]=1; hson[x]=0; for(inti=h[x];i;i=e[i].ne){ inty=e[i].to; if(y==Fa)continue; dep[y]=dep[......
  • 树链剖分学习笔记
    树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、BST、SPLAY、线段树等)来维护每一条链。——百度百科重链剖分概念1重儿子一个父节点的所有儿子中,子树节点最大(siz最大)的节点。记......
  • 链剖分
    重链剖分第一次DFS求重儿子(子节点更多的儿子)。第二次DFS求重链剖分(先访问重儿子再访问其他轻儿子)。变量:重儿子,子树大小,DFS序(\(x\)根子树DFS序中第一次出现位置),以\(x\)为根的子树的最后一次出现位置,深度,\(x\)所在重链的顶端。voiddfs1(intx){//求出重儿子,sz[v]......
  • 树链剖分
    son[x]表示节点\(x\)的重儿子,若为\(0\),则说明\(x\)为叶子节点id[x]表示节点\(x\)的dfs序编号f[x]表示节点\(x\)的父节点dep[x]表示节点\(x\)的深度sz[x]表示节点为\(x\)的子树的大小top[x]表示x所在重链顶部的节点函数原型voiddfs1(llu,llfa)功能:计算dep[]、f[]、sz[]和......
  • lncLocator 2.0:具有可解释深度学习的长链非编码rna的细胞系特异性亚细胞定位预测器
    lncLocator2.0:acell-line-specificsubcellularlocalizationpredictorforlongnon-codingRNAswithinterpretabledeeplearnin关键词:长链非编码RNA亚细胞定位预测;可解释模型;词嵌入;端对端;作者:YangLin,XiaoyongPan*andHong-BinShen期刊:Bioinformatics年份:2022......