线段树进阶

动态开点线段树

当权值线段树的值域很大时, 通常有两种解决方法
离散化或者是动态开点
理解性记下
query 没点的时候直接返回
区间修改最多开2logV个节点
单点修改最多开logV个节点
首先开一个root, tot表示根和节点个数
对于一次访问 若不存在此节点那么新开一个节点 (注意引用符号是精髓)
pushdown的时候要先把儿子节点开好

#include <bits/stdc++.h>
#define ls t[cur].lson
#define rs t[cur].rson
typedef unsigned long long ll;
using namespace std;
const int N = 1e5 + 10;
struct node
{
	int lson, rson;
	ll sm, tag;
	void f(ll val, int len)
	{
		tag += val;
		sm += val * len;
	}
}t[N * 66];
int root, tot;
inline void push_down(int cur, int l, int r)
{
	node &p = t[cur];
	if(p.tag)
	{
		int mid = l + r >> 1;
		if(!ls) ls = ++ tot;
		if(!rs) rs = ++ tot;
		t[ls].f(p.tag, mid - l + 1); t[rs].f(p.tag, r - mid);
		p.tag = 0;
	}
}
void modify(int &cur, int l, int r, int x, int y, ll val)
{
	if(!cur) cur = ++ tot; // 好习惯初始化一下 
	if(x <= l && r <= y)
	{
		t[cur].f(val, r - l + 1); return;
	}
	push_down(cur, l, r);
	int mid = l + r >> 1;
	if(x <= mid) modify(ls, l, mid, x, y, val);
	if(y > mid) modify(rs, mid + 1, r, x, y, val);
	t[cur].sm = (ls ? t[ls].sm : 0) + (rs ? t[rs].sm : 0);
}
ll query(int &cur, int l, int r, int x, int y)
{
	if(!cur) return 0;
	if(x <= l && r <= y) return t[cur].sm;
	push_down(cur, l, r);
	int mid = l + r >> 1; ll res = 0;
	if(x <= mid) res += query(ls, l, mid, x, y);
	if(y > mid) res += query(rs, mid + 1, r, x, y);
	return res;
}
int main()
{
	ios :: sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int n, m; cin >> n >> m;
	while(m -- )
	{
		int op; cin >> op;
		if(op == 1)
		{
			int l, r, k; cin >> l >> r >> k;
			modify(root, 1, n, l, r, k);
		}
		else
		{
			int l, r; cin >> l >> r;
			cout << query(root, 1, n, l, r) + (l+r)*(ll)(r-l+1)/2 << '\n';
		}
	}
	return 0;
}

放一个模板, 有区间修改空间就是2logV的

线段树合并 例题

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a); i <= (b); i++)
#define repf(i, a, b) for (int i = (a); i >= (b); i--)
#define ls t[cur].lson
#define rs t[cur].rson
typedef long long ll;
using namespace std;
const int N = 1e5 + 2e5;
const int P = 1e9 + 7;
int a[N], root[N], tot;
vector<int> v[N];
struct node
{
	int lson, rson;
	// 要求个数最多的颜色是mx个  颜色个数 = mx 那么我们统计这样的颜色的编号之和为ans 
	ll mx, ans;
}t[N * 34];
inline void merge(node &a, node b, node c)
{
	if(b.mx == c.mx)
	{
		a.mx = b.mx; a.ans = b.ans + c.ans; return;
	}
	a.mx = max(b.mx, c.mx);
	if(a.mx == b.mx) a.ans = b.ans;
	else a.ans = c.ans;
}
void modify(int &cur, int l, int r, int x) // x这个颜色+1 
{
	if(!cur) cur = ++ tot;
	if(l == r)
	{
		t[cur].mx ++;
		t[cur].ans = l;
		return;
	}
	int mid = l + r >> 1;
	if(x <= mid) modify(ls, l, mid, x);
	else modify(rs, mid + 1, r, x);
	merge(t[cur], t[ls], t[rs]);
}
int MD(int l, int r, int x, int y)
{
	if(!x || !y) return x | y;
	if(l == r)
	{
		t[x].mx += t[y].mx;
		return x;
	}
	int mid = l + r >> 1;
	t[x].lson = MD(l, mid, t[x].lson, t[y].lson);
	t[x].rson = MD(mid + 1, r, t[x].rson, t[y].rson);
	merge(t[x], t[t[x].lson], t[t[x].rson]);
	return x;
}
ll ans[N];
void dfs(int u, int fa)
{
	modify(root[u], 1, 100000, a[u]);
	for(auto x : v[u])
	{
		if(x == fa) continue;
		dfs(x, u);
		root[u] = MD(1, 100000, root[u], root[x]);
	}
	ans[u] = t[root[u]].ans;
}
int main() 
{
    ios ::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n; cin >> n;
    rep(i, 1, n) cin >> a[i];
    rep(i, 1, n - 1)
    {
    	int x, y; cin >> x >> y;
    	v[x].push_back(y), v[y].push_back(x);
	}
	dfs(1, -1);
	rep(i, 1, n) cout << ans[i] << ' ';
    return 0;
}

开n颗线段树, 第i颗线段树存的就是,以第i个点为根的时候子树内的颜色最多的颜色编号之和
求第i颗线段树就是将他的所有儿子合并加上自己即可
线段树合并只在叶子节点进行,上面的push_up即可
精髓

int MD(int l, int r, int x, int y)
{
	if(!x || !y) return x | y;
	if(l == r)
	{
		t[x].mx += t[y].mx; // 这一部分应该根据题目写合并
		return x;
	}
	int mid = l + r >> 1;
	t[x].lson = MD(l, mid, t[x].lson, t[y].lson);
	t[x].rson = MD(mid + 1, r, t[x].rson, t[y].rson);
	merge(t[x], t[t[x].lson], t[t[x].rson]);
	return x;
}
posted @ 2025-09-11 17:07  闫柏军  阅读(3)  评论(0)    收藏  举报