ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树上问题乱做

2022-02-08 16:03:50  阅读:160  来源: 互联网

标签:int ll 差分 问题 edge lca 树上 dis


DFS序

DFS 序就是DFS得到的序列(简洁明了

DFS 序擅长处理子树的问题, 可以发现 子树是这个子树根节点入栈到出栈的整个序列

DFS 序把子树修改转化为了区间修改的问题。

「HAOI2015」树上操作

要对一个树进行操作, 支持点修改, 子树修改, 查询某个节点到跟节点的距离。

对于树剖就是版子题, 但是我太菜了, 所以只好用 DFS 序解决。

前两个操作, 用 DFS 序可以轻松结局。 但是对于第三个操作需要认真的思考。

如何求点到根的距离成为了本题的关键。

观察节点 x 到根的DFS序的关系, 可以发现凡是在 1 ~ x 之间的出栈了的节点都没有在这条路径上, 那就可以把已经出栈的节点的权值抵消掉再求前缀和。

所以把入栈标记为 1, 把出栈标记为 -1。

用线段树维护这个序列。

对于操作一, 入栈增加 a, 出栈增加 -a。

对于操作二,用线段树进行区间修改, 用懒惰标记优化。

对于操作三,直接求 1 - in[x] 的前缀和。

// the code is from zxy
const int N = 100010;
typedef long long ll;
struct node {ll v, f; } d[N << 1];
struct G {ll dis, next;} edge[N << 1];
ll head[N + 1], siz;
inline void add (ll from, ll dis) {
	edge[++siz].dis = dis;
	edge[siz].next = head[from];
	head[from] = siz;
}
ll n, m;
ll a[N + 1], in[N + 1], out[N + 1],  S, sum[N << 1];
struct TREE {
	#define lson l, m, rt << 1
	#define rson m + 1, r, rt << 1 | 1
	#define ls rt << 1
	#define rs rt << 1 | 1
	ll val[N << 3], tag[N << 3];
	#define val(rt) val[rt]
	#define tag(rt) tag[rt]
	
	inline void pushup(ll rt) {val(rt) = val(ls) + val(rs);}
	inline void pushdown(ll l, ll m, ll r, ll rt) {
		if (!tag(rt)) return;
		val(ls) += tag(rt) * (sum[m] - sum[l - 1]), val(rs) += tag(rt) * (sum[r] - sum[m]);
		tag(ls) += tag(rt), tag(rs) += tag(rt), tag(rt) = 0;
	}
	inline void build(ll l, ll r, ll rt) {
		if (l == r) {val(rt) = d[l].f * a[d[l].v]; return;}
		ll m = l + r >> 1;
		build(lson), build(rson);
		pushup(rt);
	}
	// 单点修改 
	inline void update(ll pos, ll V, ll l, ll r, ll rt) {
		val(rt) += V;
		if (l == r) return ;
		ll m = l + r >> 1;
		if (pos <= m) update(pos, V, lson);
		else update(pos, V, rson);
	}
	// 区间修改 
	inline void update2(ll L, ll R, ll V, ll l, ll r, ll rt) {
		if (L <= l && r <= R) {
			tag(rt) += V;
			val(rt) += (ll) (V * (sum[r] - sum[l - 1]));
			return ;
		}
		ll m = l + r >> 1;
		pushdown(l, m, r, rt);
		if (L <= m) update2(L, R, V, lson);
		if (R > m) update2(L, R, V, rson);
		pushup(rt); 
	}
	inline ll ask(ll L, ll R, ll l, ll r, ll rt) {
		if (L <= l && r <= R) return val(rt);
		ll m = l + r >> 1;
		ll ans = 0;
		pushdown(l, m, r, rt);
		if (L <= m) ans += ask(L, R, lson);
		if (R > m)  ans += ask(L, R, rson);
		return ans;
	}
} tree;
inline void dfs(ll fa, ll u) {
	S++, in[u] = S, d[S].f = 1, d[S].v = u;
	for (ll i = head[u]; i; i = edge[i].next) if (fa != edge[i].dis) dfs(u, edge[i].dis);
	S++, out[u] = S,  d[S].f = -1, d[S].v = u;
}
int main() {
	scanf("%lld%lld", &n, &m);
	for (ll i = 1; i <= n; i++) scanf("%lld", &a[i]);
	for (ll i = 1, v, u; i < n; i++) scanf("%lld%lld", &u, &v), add(u, v), add(v, u);
	dfs(0, 1);
	for (ll i = 1; i <= S; i++) sum[i] = sum[i - 1] + d[i].f;
	n <<= 1;
	tree.build(1, n, 1);
	while (m--) {
		ll op, x, A; scanf("%lld %lld", &op, &x);
		if (op == 1) {
			scanf("%lld", &A);
			tree.update(in[x], A, 1, n, 1);
			tree.update(out[x], -A, 1, n, 1);
		} else if (op == 2) {
			scanf("%lld", &A);
			tree.update2(in[x], out[x], A, 1, n, 1);
		} else printf("%lld\n", tree.ask(1, in[x], 1, n, 1));
	}	
	return 0;
}

代码长度就不说了。

树上差分

树上差分, 顾名思义就是在树上的差分, 适合修改树上任意路径的值。

点差分

p[u] 表示经过 u 和 u 的祖先节点的次数。

f[u] 表示经过 u 的次数。
$$
如果用 siz_u 表示这个子树的大小,那么 f_u = p_u+ \sum_{v = 1}^{siz_u}f_v
$$
对于修改 x, y 这条路径,即让这个路径上的数全部加上 d,只需要修改四个值 :

p[x] += d, p[y] += d;
p[lca(x, y)] -=d, p[fa[lca(x, y)]] -= d;

对于前两个操作就是让 1 - x, 1 - y 这两段加上 d, 显然我们只想让 lca(x, y) 到x, y的这一段修改,不希望lca(x,y)以上的段修改。

所以就有了后两个操作,先删除一次 lca(x,y) 到根节点这一段, 由于 x 到 y 的这一段包括了 lca(x, y) 所以我们只好再减去 fa[lca(x, y)] 到根节点的这一段, 保证路径都只有一段被修改。

这个差分的操作使路径修改成为了常数级别。

「JLOI2014」松鼠的新家

模板题。维尼到达一个点, 这个点就多需要一个糖果, 所以问题简化成维尼每到达一个点, 这个点的权值就需要增加 1。 还要在特判一下, 第一个点要减去 1, 因为维尼是从这里开始走的。

// the code is from zxy
const int N = 300000;
struct node {
	int dis, next;
} edge[N << 1];
int siz, head[N + 1];
inline void add(int from, int dis) {
	edge[++siz].dis = dis;
	edge[siz].next = head[from]; 
	head[from] = siz;
}
int d[N];
int f[N + 1][30];
inline int lca(int x, int y) {
	if (d[x] > d[y]) swap(x, y);
	for (int i = 23; i >= 0; i--) if (d[f[y][i]] >= d[x]) y = f[y][i];
	if (x == y) return x;
	for (int i = 23; i >= 0; i--) if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}
inline void dfs(int u, int fa) {
	d[u] = d[fa] + 1;
	f[u][0] = fa;
	for (int i = 1; (1 << i) <= d[u]; i++) f[u][i] = f[f[u][i - 1]][i - 1];
	for (int i = head[u]; i; i = edge[i].next) if (edge[i].dis != fa) dfs(edge[i].dis, u);
}
int c[N + 1];
int n, ans[N + 1], a[N + 1];
inline int find(int u) {
	ans[u] = c[u];
	for (int i = head[u]; i; i = edge[i].next) 
		if (edge[i].dis != f[u][0]) ans[u] += find(edge[i].dis); 
	return ans[u];
}
int main(){
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i < n; i++) {
		int u, v; scanf("%d%d", &u, &v);
		add(u, v); add(v, u);
	}
	dfs(1, 0);
	for (int i = 1; i < n; i++) {
		int l = lca(a[i], a[i + 1]);
		c[l]--, c[f[l][0]]--;
		c[a[i]]++, c[a[i + 1]]++;
	}
	find(1);
	for (int i = 1; i <= n; i++) {
		if (i != a[1]) ans[i]--;
		printf("%d\n", ans[i]);
	}
	return 0;
}

边差分

类似的。

对于边差分。
$$
我们设 f_u 表示经过点u和u的祖先的边的次数。
$$
如果要让 xy 的边权加 d

f[x]++, f[y]++;
f[lca(x, y)] -= 2;

不同于点差分,边差分不需要算重复的 lca(x, y), 直接让那条边减去多统计的2次就好了。

「POJ3417」暗的连锁

一个无向图有两种边, 切断一条主要边 和一条附加边, 使这个图不连通, 求方案数量。

可以发现主要边是一个树。而附加边似乎是多余的。

如何判断联通是本题的关键, 观察下图:

/upload/user/yx20230603/22012303189074.png

蓝色的是主要边, 暗红色的是附加边, 下图中打勾的是可以删除的, 打叉的是不可以删除的。

/upload/user/yx20230603/22012303264087.png

可以发现主要边这个树因为有了附加边出现了环, 这里环代表多条主要边和一条附加边。如上图中环就有:
$$
1\ 2\ 5, 2\ 3\ 5, 1\ 2\ 4\ 6 \dots
$$
观察发现无法删除的只是存在于多个环中的边。

考虑一条边没有在环里,删除这条边后,任意删除一条附加边都可以使图不连通, 方案书就是 m

一条边出现在一个环里,删除这条边后,附加边的删除是唯一的,方案数就是 1

一条边出现在多个环里,删除这条边后,无论删除那条附加边都无法使图不连通,方案数为 0。

那如何维护环的加减呢?

可以借助边差分的力量。

既然环是只有一条附加边,那每出现一条附加边就可以先用差分维护出现次数。

// the code is from zxy
const int N = 500100;
struct node {int dis, next;} edge[N + 1];
int siz, head[N + 1];
int n, m, u, v, ans;
inline void add(int from, int dis) {
	edge[++siz].dis = dis;
	edge[siz].next = head[from]; 
	head[from] = siz;
}
int f[N + 1][30], d[N + 1], p[N + 1];
inline void bfs() {
	queue<int> q; q.push(1); d[1] = 1;
	while (!q.empty()){
		int u = q.front(); q.pop();
		for (int i = head[u]; i; i = edge[i].next) {
			int dis = edge[i].dis;
			if (d[dis]) continue;
			d[dis] = d[u] + 1;
			f[dis][0] = u;
			for (int j = 1; j <= 23; j++)
				f[dis][j] = f[f[dis][j - 1]][j - 1];
			q.push(dis);
		}
	}
}
inline int lca(int x, int y) {
	if (d[x] > d[y]) swap(x, y);
	for (int i = 23; i >= 0; i--) if (d[f[y][i]] >= d[x]) y = f[y][i];
	if (x == y) return x;
	for (int i = 23; i >= 0; i--) if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}
int vis[N + 1];
inline void dfs(int u) {
	vis[u] = 1;
	for (int i = head[u]; i; i = edge[i].next) {
		int dis = edge[i].dis;
		if (vis[dis]) continue;
		dfs(dis);
		p[u] += p[dis];
	}
}
int main(){
	scanf("%d%d", &n, &m);
	for (int i = 1; i < n; i++) {
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
	}
	bfs();
	for (int i = 1; i <= m; i++) {
		scanf("%d%d", &u, &v);
		p[u]++ , p[v]++;
		p[lca(u, v)] -= 2; 
	}
	dfs(1);
	for (int i = 2; i <= n; i++) {
		if (!p[i]) ans += m;
		if (p[i] == 1) ans++; 
	}
	printf("%d\n", ans);
	return 0;
}

标签:int,ll,差分,问题,edge,lca,树上,dis
来源: https://www.cnblogs.com/Zheng-XiangYu/p/15871569.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有