线段树进阶
动态开点线段树
当权值线段树的值域很大时, 通常有两种解决方法
离散化或者是动态开点
理解性记下
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;
}