首页 > 其他分享 >【线段树合并】雨天的尾巴

【线段树合并】雨天的尾巴

时间:2024-11-07 18:57:41浏览次数:3  
标签:rs int 线段 尾巴 雨天 maxn root mx

题意

给一棵 \(n\) 个节点的无根树,共 \(m\) 次操作,每次操作是一个三元组 \((x, y, z)\),表示路径 \((x \to y)\) 全部发一袋类型为 \(z\) 的救济粮。问最后每座房子存放最多的是哪种救济粮。

思路

看到树上路径的操作,首先考虑差分,但本题的特殊之处在于每个节点有 \(10^5\) 种不同的贡献需要维护。

朴素想法是开一个二维数组 \(val[u][i]\) 表示节点 \(u\) 有多少袋种类为 \(i\) 的救济粮,这样的话时空都是 \(O(np)\),\(p\) 是救济粮的种类。(时间的瓶颈是在最后深搜统计答案)

考虑到我们最后只询问每个点最多的是哪种救济粮,因此过程中其实维护了很多无用的信息(硬要说有用的话,只用于了答案取 \(\max\)),这显然是非常费时的。

我们可以使用线段树合并,来快速地只维护每个节点的 \(max\) 救济粮。

本题显然是值域线段树,而且每个节点都要开一棵,差分部分 \(4\) 次打标记就换成 \(4\) 次单点修。

打完标记 dfs 合并答案就是真正的线段树合并部分。其原理就是 \(u,v\) 两个节点的线段树采用同步遍历,当移动到只有一方有点时,就直接用这个点,否则就遍历到叶子节点,把贡献统一放到 \(u\) 的线段树上。

关于空间,采用动态开点,每次打 \(4\) 个差分标记最多会创造 \(4\) 条新的长度为 \(\log n\) 的链,因此空间不超过 \(O(4mlogn)\)。

关于时间,其实是所有点对 \((u, v)\) 进行合并,复杂度为 \(O(mlogn)\)。

记得合并的时候要上传合并后该点的新编号。

实现

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int N = 1e5 + 5, maxn = 1e5;
struct node{
	int v, ne;
}e[N << 1];
int first[N], ans[N], idx = 0;
int n, m;
int root[N];
void add(int x, int y){
	e[++ idx] = (node){y, first[x]};
	first[x] = idx;
}
struct LCA{
	int dep[N], fa[N], dfn[N], rev[18][N], cnt = 0;
	void add(int x, int y){
		e[++ idx] = (node){y, first[x]};
		first[x] = idx;
	}
	void dfs(int u, int f){
		fa[u] = f;
		dep[u] = dep[f] + 1;
		dfn[u] = ++ cnt;
		rev[0][cnt] = u;
		for(int i = first[u]; i; i = e[i].ne){
			int v = e[i].v;
			if(v == f) continue;
			dfs(v, u);
		}
	}
	int cmin(int x, int y){
		if(dep[x] < dep[y]) return x;
		return y;
	}
	int getlca(int x, int y){
		if(x == y) return x;
		if((x = dfn[x]) > (y = dfn[y])) swap(x, y);
		int t = __lg(y - x++);
		return fa[cmin(rev[t][x], rev[t][y - (1 << t) + 1])];
	}
	void init(){
		dfs(1, 0);
		F(j, 1, 17) F(i, 1 , n - (1 << j) + 1) rev[j][i] = cmin(rev[j - 1][i], rev[j - 1][i + (1 << (j - 1))]);
	}
}lca;
struct Segtree{
	int mx[N * 50], typ[N * 50], ls[N * 50], rs[N * 50], cnt;
	void pushup(int u){
		if(mx[ls[u]] >= mx[rs[u]]){
			mx[u] = mx[ls[u]];
			typ[u] = typ[ls[u]];
		}
		else{
			mx[u] = mx[rs[u]];
			typ[u] = typ[rs[u]];
		}
	}
	void update(int &u, int l, int r, int x, int v){
		if(!u) u = ++ cnt;
		if(l == r){
			mx[u] += v;
			typ[u] = x;
			return ;
		} int mid = (l + r) >> 1;
		if(x <= mid) update(ls[u], l, mid, x, v);
		else update(rs[u], mid + 1, r, x, v);
		pushup(u);
	}
	int merge(int u, int v, int l, int r){
		if(!u || !v) return u + v;
		if(l == r){
			mx[u] += mx[v];
			return u;
		} int mid = (l + r) >> 1;
		ls[u] = merge(ls[u], ls[v], l, mid);
		rs[u] = merge(rs[u], rs[v], mid + 1, r);
		pushup(u);
		return u;
	}
	void spread(int u){
		for(int i = first[u]; i; i = e[i].ne){
			int v = e[i].v;
			if(v == lca.fa[u]) continue;
			spread(v);
			root[u] = merge(root[u], root[v], 1, maxn); // 一定记得更新 root[u] 
		}
		ans[u] = mx[root[u]] ? typ[root[u]] : 0;
	}
}tr;
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n >> m;
	F(i, 1, n - 1){
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	lca.init();
	while(m --){
		int x, y, z;
		cin >> x >> y >> z;
		int p = lca.getlca(x, y);
		tr.update(root[x], 1, maxn, z, 1);
		tr.update(root[y], 1, maxn, z, 1);
		tr.update(root[p], 1, maxn, z, -1);
		tr.update(root[lca.fa[p]], 1, maxn, z, -1);
	}
	tr.spread(1);
	F(i, 1, n) cout << ans[i] << '\n';
	return fflush(0), 0;
}

标签:rs,int,线段,尾巴,雨天,maxn,root,mx
From: https://www.cnblogs.com/superl61/p/18533784

相关文章

  • 线段树知识乱讲(施工中)
    前言算法竞赛题目考察的是选手对于数据结构的选取与算法的巧妙结合,而数据结构中线段树扮演一个至关重要的角色,而近期(CSP结束)在hfu的安排下我们需要自己弄一周的ds,所以就有了这篇奇妙的博客。线段树基础知识在我看来,线段树其实就是在数组的基础上添加了一些额外的点,这些点用......
  • 线段树(Segment Tree)详解
    写在前面:此博客内容已经同步到我的博客网站,如需要获得更优的阅读体验请前往https://blog.mainjay.cloudns.ch/blog/algorithm/segment-tree1.为什么需要线段树?1.1问题起源想象这样一个场景:有一个长度为n的数组,我们需要经常进行以下操作:查询数组中某个区间[l,r]的......
  • 【某NOIP模拟赛T2 - 旅游】--线段树优化 DP 的魅力
    题意:数轴上在起点\(s\)和终点\(t\)间的整点中有\(n\)个关键点,第\(i\)个关键点位置为\(c_i\),可获得\(m_i\)的价值。你可以从起点开始,每次跳至多\(z\)个点(跨过中间的点),而每到达一个\(s\)以外的点需要支付\(a\)的代价,求走到终点的最大价值。\(0\les\lec_i\let......
  • 【笔记/模板】线段树(改)
    线段树线段树是OI竞赛中最强大的数据结构之一,可以用来维护和、积以及最值等具有合并性质的信息。一般线段树P3372【模板】线段树1-洛谷|计算机科学教育新生态(luogu.com.cn)P3373【模板】线段树2-洛谷|计算机科学教育新生态(luogu.com.cn)以模板一为例:cla......
  • Geogebra基础篇021—Geogebra的线工具:线段、直线、射线、给定长度的线段、从点出发的
    注意:关注微信公众号“第五智能”,免费查阅全系列文章(或者微信顶部直接搜索“Geogebra的线工具”就可以找到了)。上一篇是点工具,这一篇赶紧把线工具记录一下,基础篇早日完稿我们就可以研究更有意思的动画技巧了。Geogebra的线工具主要包括:线段(Segment)、直线(Line)、射线(Ray)、给定......
  • LeetCode 3165.不包含相邻元素的子序列的最大和:单点修改的线段树(动态规划)
    【LetMeFly】3165.不包含相邻元素的子序列的最大和:单点修改的线段树(动态规划)力扣题目链接:https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements/给你一个整数数组nums和一个二维数组queries,其中queries[i]=[posi,xi]。对于每个查......
  • 线段树也能是 Trie 树 题解
    题意简述给定一个长为\(n=2^k\)的序列\(\{a_0,\ldots,a_{n-1}\}\),你需要使用数据结构维护它,支持\(m\)次以下操作:单点加:\(a_x\getsa_x+y\);区间查:\(\sum\limits_{i=l}^ra_i\);全局下标与:\(a'_{i\operatorname{and}x}\getsa_{i}\),即把\(a_i\)累加到......
  • 线段树:区间修改,区间查询
    Description这是一道模板题。给定数列 ,你需要依次进行  个操作,操作有两类:1lrx:给定 ,对于所有 ,将  加上 (换言之,将  分别加上 );2lr:给定 ,求  的值(换言之,求  的值)。Input第一行包含  个正整数 ,表示数列长度和询问个数。保证 。第二行  个整数 ,表示......
  • 线段树 & 树状数组
    线段树常用于维护区间值代码和题解有很大差异,但是过了就好voidPushup(intx){ s[x]=(s[x<<1]+s[x<<1|1]);}voidPushdown(intx,intl,intr){ s[x]=(s[x]+ad[x]*(r-l+1)); if(l!=r)ad[x<<1]=(ad[x<<1]+ad[x]); if(l!=r)ad[x<<1|1]=(ad[x<<1|1]+ad[x......
  • 计蒜客:公告板(线段树)
     难点在于要把模型抽象出来。第一眼看到题面,想到的是以公告板的高度作为线段树的区间,但看到h<=10^9以后,感觉又开不了这么大的数组。但实际上,最多只有n块公告,所以最极端的情况下不过只有n行,所以区间的真正大小是[1,min(n,h)]。解决了区间的问题,再来考虑每个节点要维护的信息。......