线段树的基本操作与线段树合并

普通线段树#

P3372 【模板1】线段树1#

使用线段树进行维护。

image-20230224233912347

#include <iostream>
#include <cstring>
#include <algorithm>

#define int long long

using namespace std;

const int N = 100010;

struct SegmentTree {
	int l, r;
	int val;
	int tag;
}tr[N << 2];

int a[N];

void pushup(int u) {
	tr[u].val = tr[u << 1].val + tr[u << 1 | 1].val;
}

void addtag(int u, int v) {
	tr[u].tag += v;
	tr[u].val += v * (tr[u].r - tr[u].l + 1);
}

void pushdown(int u) {
	if (tr[u].tag) {
		addtag(u << 1, tr[u].tag);
		addtag(u << 1 | 1, tr[u].tag);
		tr[u].tag = 0;
	}
}

void build(int u, int l, int r) {
	tr[u].l = l, tr[u].r = r;
	if (l == r) {
		tr[u].val = a[l];
		return;
	}
	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
	pushup(u);
}

int query(int u, int l, int r) {
	if (l <= tr[u].l && tr[u].r <= r) return tr[u].val;
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1, sum = 0;
	if (l <= mid) sum += query(u << 1, l, r);
	if (r > mid) sum += query(u << 1 | 1, l, r);
	return sum;
}

void modify(int u, int l, int r, int v) {
	if (l <= tr[u].l && tr[u].r <= r) {
		addtag(u, v);
		return;
	}
	pushdown(u);
	int mid = tr[u].l + tr[u].r >> 1;
	if (l <= mid) modify(u << 1, l, r, v);
	if (r > mid) modify(u << 1 | 1, l, r, v);
	pushup(u);
}

int n, m;

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	build(1, 1, n);
	
	int a, b, c, d;
	for (int i = 1; i <= m; i++) {
		cin >> a;
		if (a == 1) {
			cin >> b >> c >> d;
			modify(1, b, c, d);
		}
		else {
			cin >> b >> c;
			cout << query(1, b, c) << '\n';
		}
	}
	return 0;
}

P4588 [TJOI2018]数学计算#

以操作为元素建立一棵线段树,维护乘积(要 modM)。

对于第 i 次操作,如果操作类型为1,就把线段树第 i 个位置上的值改为 x;如果操作类型为2,就把线段树第 x 个位置上的值改为 1 即可。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;

struct SegmentTree {
	int l, r;
	int val;
}tr[N << 2];

int n, m;

void pushup(int u) {
	tr[u].val = (1ll * tr[u << 1].val * tr[u << 1 | 1].val)% m;
}

void build(int u, int l, int r) {
	tr[u].l = l, tr[u].r = r, tr[u].val = 1;
	if (l == r) return;
	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
}

void modify(int u, int x, int v) {
	if (tr[u].l == tr[u].r) {
		tr[u].val = v;
		return;
	}

	int mid = tr[u].l + tr[u].r >> 1;
	if (x <= mid) modify(u << 1, x, v);
	else modify(u << 1 | 1, x, v);
	pushup(u);
}

void solve() {
	cin >> n >> m;
	build(1, 1, n);
	int opt, x;
	for (int i = 1; i <= n; i++) {
		cin >> opt >> x;
		if (opt == 1) modify(1, i, x);
		else modify(1, x, 1);
		cout << tr[1].val << '\n';
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int T;
	cin >> T;
	while (T--) solve();

	return 0;
}

P1471 方差#

首先可以想到这是使用线段树的。

我们对方差公式 s2=1ni=1n(AiA)2 进行转化。

image

所以我们要维护一个 i=1na[i],以及 i=1na[i]

如果对区间全部 +k,那么可以直接加上红色部分。
image

#include <iostream>
#include <cstring>
#include <algorithm>
#include <iomanip>

using namespace std;

const int N = 100010;

struct SegmentTree {
	double sum;			// Σai
	double get;			// Σai²

	double tag;
}tr[N * 4];

int n, m;
double a[N];

void tag(int u, int l, int r, double v) {
	tr[u].tag += v;
	tr[u].get += v * v * (r - l + 1) + 2 * v * tr[u].sum;
	tr[u].sum += v * (r - l + 1);
}

void pushdown(int u, int l, int r) {
	if (tr[u].tag) {
		int mid = l + r >> 1;
		tag(u << 1, l, mid, tr[u].tag);
		tag(u << 1 | 1, mid + 1, r, tr[u].tag);
		tr[u].tag = 0;
	}
}

void pushup(SegmentTree& ans, SegmentTree l, SegmentTree r) {
	ans.sum = l.sum + r.sum;
	ans.get = l.get + r.get;
}

void pushup(int u) {
	pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r) {
	if (l == r) {
		tr[u].sum = a[l];
		tr[u].get = a[l] * a[l];
		return;
	}

	int mid = l + r >> 1;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
	pushup(u);
}

void modify(int u, int l, int r, int pl, int pr, double v) {
	if (pl <= l && r <= pr) {
		tag(u, l, r, v);
		return;
	}

	pushdown(u, l, r);
	int mid = l + r >> 1;
	if (pl <= mid) modify(u << 1, l, mid, pl, pr, v);
	if (pr > mid) modify(u << 1 | 1, mid + 1, r, pl, pr, v);

	pushup(u);
}

SegmentTree query(int u, int l, int r, int pl, int pr) {
	if (pl <= l && r <= pr) {
		return tr[u];
	}
	pushdown(u, l, r);
	int mid = l + r >> 1;
	if (pr <= mid) return query(u << 1, l, mid, pl, pr);
	else if (pl > mid) return query(u << 1 | 1, mid + 1, r, pl, pr);
	else {
		SegmentTree lans, rans, ans;
		lans = query(u << 1, l, mid, pl, pr);
		rans = query(u << 1 | 1, mid + 1, r, pl, pr);
		pushup(ans, lans, rans);
		return ans;
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout << fixed << setprecision(4);

	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];

	build(1, 1, n);

	int opt, a, b;
	double c;
	while (m--) {
		cin >> opt;
		if (opt == 1) {
			cin >> a >> b >> c;
			modify(1, 1, n, a, b, c);
		}
		else if (opt == 2) {
			cin >> a >> b;
			SegmentTree ans = query(1, 1, n, a, b);
			double avg = ans.sum / (b - a + 1);
			cout << avg << '\n';
		}
		else {
			cin >> a >> b;
			SegmentTree ans = query(1, 1, n, a, b); 
			int len = b - a + 1;
			double avg = ans.sum / len;
			double res = 1.0 / len * (ans.get + len * avg * avg - 2 * avg * ans.sum);
			cout << res << '\n';
		}
	}
	return 0;
}

动态开点线段树#

即在需要时才开点。

P3372 【模板1】线段树1#

强制使用动态开点线段树来做这一题。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
using i64 = long long;

const int N = 100010;

struct SegmentTree {
    int l, r;
    i64 sum, tag;
}tr[N << 1];

int tot = 1, root = 1;

int build() {
    tot++, tr[tot].l = tr[tot].r = tr[tot].sum = tr[tot].tag = 0;
    return tot;
}

void tag(int& cur, int l, int r, i64 v) {
    if (!cur) cur = build();
    tr[cur].tag += v;
    tr[cur].sum += v * (r - l + 1);
}

void pushup(int cur) {
    tr[cur].sum = tr[tr[cur].l].sum + tr[tr[cur].r].sum;
}

void pushdown(int cur, int l, int r) {
    if (l >= r || !tr[cur].tag) return;
    int mid = (l + r - 1) / 2;
    tag(tr[cur].l, l, mid, tr[cur].tag);
    tag(tr[cur].r, mid + 1, r, tr[cur].tag);
    tr[cur].tag = 0;
}

void add(int& cur, int l, int r, int _left, int _right, i64 val) {
    if (!cur) cur = build();
    if (_left <= l && r <= _right) {
        tag(cur, l, r, val);
        return;
    }
    pushdown(cur, l, r);
    int mid = (l + r - 1) / 2;
    if (_left <= mid) add(tr[cur].l, l, mid, _left, _right, val);
    if (_right > mid) add(tr[cur].r , mid + 1, r, _left, _right, val);
    pushup(cur);
}

i64 query(int& cur, int l, int r, int _left, int _right) {
    if (!cur) return 0;
    if (_left <= l && r <= _right) {
        return tr[cur].sum;
    }
    pushdown(cur, l, r);
    int mid = (l + r - 1) / 2;
    i64 sum = 0;
    if (_left <= mid) sum += query(tr[cur].l, l, mid, _left, _right);
    if (_right > mid) sum += query(tr[cur].r , mid + 1, r, _left, _right);
    return sum;
}

int n, m;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        add(root, 1, n, i, i, x);
    }


    int op, x, y;
    i64 z;
    while (m--) {
        cin >> op;
        if (op == 1) {
            cin >> x >> y >> z;
            add(root, 1, n, x, y, z);
        }
        else {
            cin >> x >> y;
            cout << query(root, 1, n, x, y) << '\n';
        }
    }
    return 0;
}

权值线段树#

线段树维护的区间是一个值域范围,而不是一段下标的区间。

P1637 三元上升子序列#

我们可以考虑中间的数字 x,统计前面比 x 小的数字个数 a1, 统计后面比 x 大的数字个数 a2,最后的结果为 a1×a2

image

使用权值线段树,边处理边询问,从前往后枚举 a[i],先统计 [1,a[i]1] 的位置上的数字总和,然后再在位置 a[i]+1,这样统计出来的都是之前比 a[i] 小的数字。

统计比 a[i] 大的数字同理,只要反过来统计,即从 a[n] 枚举到 a[1], 然后询问 [a[i]+1,n] 的数字总和即可。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
using i64 = long long;

const int N = 100010;

struct SegmentTree {
    int l, r;
    int val;
}tr[N << 2];

int a[N], n;

void pushup(int u) {
    tr[u].val = tr[u << 1].val + tr[u << 1 | 1].val;
}

void build(int u, int l, int r) {
    tr[u].l = l, tr[u].r = r;

    if (l == r) {
        tr[u].val = 0;
        return;
    }

    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

void modify(int u, int x, int v) {
    if (tr[u].l == x && x == tr[u].r) {
        tr[u].val += v;
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if (x <= mid) modify(u << 1, x, v);
    else modify(u << 1 | 1, x, v);
    pushup(u);
}

int query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) {
        return tr[u].val;
    }
    
    int mid = tr[u].l + tr[u].r >> 1, sum = 0;
    if (l <= mid) sum += query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

int small[N], big[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, N - 1);

    for (int i = 1; i <= n; i++) {
        small[i] = query(1, 1, a[i] - 1);
        modify(1, a[i], 1);
    }

    build (1, 1, N - 1);

    for (int i = n; i >= 1; i--) {
        big[i] = query(1, a[i] + 1, N - 1);
        modify(1, a[i], 1);
    }

    i64 ans = 0;
    for (int i = 1; i <= n; i++) ans += 1ll * small[i] * big[i];
    cout << ans << '\n';
    return 0;
}

P1908 逆序对#

与上一题类似,但是需要离散化。

离散化:是指将大的值域范围对应到小的值域范围,且不改变元素之间的相对大小。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <unordered_map>

using namespace std;
using i64 = long long;

const int N = 1000010;

struct SegmentTree {
    int l, r;
    int val;
}tr[N << 2];

int a[N], b[N], tot, n;

void pushup(int u) {
    tr[u].val = tr[u << 1].val + tr[u << 1 | 1].val;
}

void build(int u, int l, int r) {
    tr[u].l = l, tr[u].r = r;

    if (l == r) {
        tr[u].val = 0;
        return;
    }

    int mid = l + r >> 1;
    build(u << 1, l, mid);
    build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

void modify(int u, int x, int v) {
    if (tr[u].l == x && x == tr[u].r) {
        tr[u].val += v;
        return;
    }
    int mid = tr[u].l + tr[u].r >> 1;
    if (x <= mid) modify(u << 1, x, v);
    else modify(u << 1 | 1, x, v);
    pushup(u);
}

int query(int u, int l, int r) {
    if (l <= tr[u].l && tr[u].r <= r) {
        return tr[u].val;
    }
    
    int mid = tr[u].l + tr[u].r >> 1, sum = 0;
    if (l <= mid) sum += query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

int small[N], big[N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    memcpy(b, a, sizeof(a));
    sort(b + 1, b + n + 1);
    tot = unique(b + 1, b + n + 1) - b - 1;
    for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
    build(1, 1, tot);

    for (int i = 1; i <= n; i++) {
        small[i] = query(1, a[i] + 1, tot);
        modify(1, a[i], 1);
    }
    i64 ans = 0;
    for (int i = 1; i <= n; i++) ans += small[i];
    cout << ans << '\n';
    return 0;
}

P5094 [USACO04OPEN] MooFest G#

暴力:

    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            ans += 1ll * abs(x[i] - x[j]) * max(v[i], v[j]);
        }
    }

优化策略:

  1. 按照 v 值将牛从小到大排序;

  2. 枚举牛 i,计算牛 i, 与前 i1 头牛产生的音量之和。

sum

=a[i].v×dis(i,j),其中 j[1,i1] 之间

=a[i].v×(a[i].xa[p].x)+a[i].v×(a[q].xa[i].x), 其中 p 指的是比牛 i 小的牛的下标,q 反之

=a[i].v×((a[i].xa[p].x)+(a[q].xa[i].x))

=a[i].v×(cnt1×a[i].xa[p].x+a[q].xcnt2×a[i].x),其中 cnt1 表示 p 的数量,cnt2 表示 q 的数量

可以建立两棵线段树,来快速得出 cnt1cnt2 以及 a[p].xa[q].x

第一棵:以坐标为区间,以牛数为元素,维护 sum

第二棵:以坐标为区间,以牛的坐标为元素,维护 sum

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
using i64 = long long;
using PIL = pair<int, i64>;

const int N = 50010;

int n;

struct cow {
	int x, v;
}a[N];

struct SegmentTree {
	int l, r;
	int cnt;
	i64 sum;
}tr[N << 2];

void pushup(int u) {
	tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt;
	tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r) {
	tr[u].l = l;
	tr[u].r = r;
	if (l == r) return;

	int mid = (l + r - 1) / 2;
	build(u << 1, l, mid);
	build(u << 1 | 1, mid + 1, r);
}

PIL query(int u, int l, int r) {
	if (l > r) return { 0, 0 };
	if (l <= tr[u].l && tr[u].r <= r) {
		return { tr[u].cnt, tr[u].sum };
	}

	int mid = (tr[u].l + tr[u].r - 1) / 2;
	PIL ans = { 0, 0 };
	if (l <= mid) {
		PIL get = query(u << 1, l, r);
		ans.first += get.first;
		ans.second += get.second;
	}
	if (r > mid) {
		PIL get = query(u << 1 | 1, l, r);
		ans.first += get.first;
		ans.second += get.second;
	}
	return ans;
}

void modify(int u, int x) {
	if (tr[u].l == tr[u].r) {
		tr[u].cnt++;
		tr[u].sum += x;
		return;
	}

	int mid = (tr[u].l + tr[u].r - 1) / 2;
	if (x <= mid) modify(u << 1, x);
	else modify(u << 1 | 1, x);
	pushup(u);
}

i64 ans, possum;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> n;
	build(1, 1, N - 1);
	for (int i = 1; i <= n; i++) cin >> a[i].v >> a[i].x;
	sort(a + 1, a + n + 1, [](const cow& a, const cow& b) { return a.v < b.v; });

	for (int i = 1; i <= n; i++) {
		PIL q1 = query(1, 1, a[i].x - 1);
		PIL q2 = { i - q1.first - 1, possum - q1.second};
		i64 cnt1 = q1.first, sum1 = q1.second;
		i64 cnt2 = q2.first, sum2 = q2.second;
		ans += a[i].v * (cnt1 * a[i].x - sum1 - cnt2 * a[i].x + sum2);
		modify(1, a[i].x);
		possum += a[i].x;
	}
	cout << ans << '\n';
	return 0;
}

线段树合并#

CF600E Lomsat gelral#

在每个节点上建立一棵权值线段树,然后每个节点还会记录其子树(指线段树)中颜色出现最多的数量以及编号之和。

最后自下而上将线段树合并即可,即将子树信息合并到父结点上(指题目中的树),合并时需要将次数累加。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;
using i64 = long long;

const int N = 100010;

struct SegmentTree {
	int l, r;
	i64 val;
	i64 max;
}tr[N * 70];

int root[N], tot;
int n;

void pushup(int cur) {
	if (tr[tr[cur].l].val < tr[tr[cur].r].val) tr[cur].val = tr[tr[cur].r].val, tr[cur].max = tr[tr[cur].r].max;
	else if (tr[tr[cur].l].val > tr[tr[cur].r].val) tr[cur].val = tr[tr[cur].l].val, tr[cur].max = tr[tr[cur].l].max;
	else tr[cur].val = tr[tr[cur].l].val, tr[cur].max = tr[tr[cur].l].max + tr[tr[cur].r].max;
}

void modify(int& cur, int _left, int _right, int x, i64 v) {
	if (!cur) cur = ++tot;
	if (_left == x && _right == x) {
		tr[cur].val += v;
		tr[cur].max = x;
		return;
	}
	
	int mid = (_left + _right - 1) / 2;
	if (x <= mid) modify(tr[cur].l, _left, mid, x, v);
	else modify(tr[cur].r, mid + 1, _right, x, v);
	pushup(cur);
}

struct Edge {
	int to, next;
}e[N * 2];

int head[N], idx;

void add(int a, int b) {
	idx++, e[idx].to = b, e[idx].next = head[a], head[a] = idx;
	idx++, e[idx].to = a, e[idx].next = head[b], head[b] = idx;
}

int merge(int cur1, int cur2, int _left, int _right) {
	if ((!cur1) || (!cur2)) return cur1 | cur2;
	if (_left == _right) {
		tr[cur1].val += tr[cur2].val;
		tr[cur1].max = _left;
		return cur1;
	}
	
	int mid = (_left + _right - 1) / 2;
	tr[cur1].l = merge(tr[cur1].l, tr[cur2].l, _left, mid);
	tr[cur1].r = merge(tr[cur1].r, tr[cur2].r, mid + 1, _right);
	pushup(cur1);
	return cur1;
}

i64 ans[N];

void dfs(int u, int fa) {
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs(to, u);
		root[u] = merge(root[u], root[to], 1, N - 1);
	}
	
	if (tr[root[u]].val != 0) {
		ans[u] = tr[root[u]].max;
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int x;
		cin >> x;
		modify(root[i], 1, N - 1, x, 1);
	}
	
	for (int i = 1; i < n; i++) {
		int x, y;
		cin >> x >> y;
		add(x, y);
	}
	
	dfs(1, 0);
	
	for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
	return 0;
}

CF246E Blood Cousins Return#

  1. 对询问进行离线处理
  2. 对于每个树上节点 x ,以深度为元素建立一个线段树,元素数据类型为set<string>。当进行合并时,可以把相同深度的set进行合并。
  3. 当一个查询为 (x,y) 时,输出 x 节点上线段树的第 dep[x]+y 个元素的 set的大小size即可。
  4. 同时本题只需要访问线段树的叶子节点,所以不需要pushup操作(否则会MLE)。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <set>
#include <unordered_map>

using namespace std;
using PII = pair<int, int>;

const int N = 100010;

struct Edge {
	int to, next;
}e[N * 2];

int head[N], idx;

void add(int a, int b) {
	idx++, e[idx].to = b, e[idx].next = head[a], head[a] = idx;
}

struct SegmentTree {
	int l, r;
	set<string> s;
}tr[N * 20];

int root[N], tot;

void mergeset(set<string>& a, set<string>& b) { 			// a<=b
	for (auto& x : b) a.insert(x);
}

void modify(int& cur, int l, int r, int x, string s) {
	if (!cur) cur = ++tot;
	if (l == r) {
		tr[cur].s.insert(s);
		return;
	}
	
	int mid = (l + r - 1) / 2;
	
	if (x <= mid) modify(tr[cur].l, l, mid, x, s);
	else modify(tr[cur].r, mid + 1, r, x, s);
}

int n, m;
vector<PII> query[N];
int ans[N];

int merge(int cur1, int cur2, int l, int r) {
	if ((!cur1) || (!cur2)) return cur1 | cur2;
	if (l == r) {
		mergeset(tr[cur1].s, tr[cur2].s);
		return cur1;
	}
	
	int mid = (l + r - 1) / 2;
	tr[cur1].l = merge(tr[cur1].l, tr[cur2].l, l, mid);
	tr[cur1].r = merge(tr[cur1].r, tr[cur2].r, mid + 1, r);
	return cur1;
}

int ask(int cur, int l, int r, int x) {
	if (!cur) return 0;
	if (l == r) {
		return tr[cur].s.size();
	}
	int mid = (l + r - 1) / 2;
	if (x <= mid) return ask(tr[cur].l, l, mid, x);
	else return ask(tr[cur].r, mid + 1, r, x);
}

int dep[N];
string s[N];

void dfs1(int u, int fa) {
	dep[u] = dep[fa] + 1;
	modify(root[u], 1, N - 1, dep[u], s[u]);
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs1(to, u);
	}
}

void dfs2(int u, int fa) {
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs2(to, u);
		root[u] = merge(root[u], root[to], 1, N - 1);
	}
	for (int i = 0; i < query[u].size(); i++) {
		int d = query[u][i].first, id = query[u][i].second;
		ans[id] = ask(root[u], 1, N - 1, dep[u] + d);
	}
}

vector<int> treeroot;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int fa;
		cin >> s[i] >> fa;
		if (fa == 0) {
			treeroot.push_back(i);
			continue;
		}
		add(fa, i);
		add(i, fa);
	}
	for (auto& x : treeroot) dfs1(x, 0);
	cin >> m;
	for (int i = 1; i <= m; i++) {
		int x, y;
		cin >> x >> y;
		query[x].push_back({y, i});
	}
	for (auto& x : treeroot) dfs2(x, 0);
	for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
	return 0;
}

CF208E Blood Cousins#

跟CF246E的题目名称长得很像

通过倍增,求出 xy 级祖先 g,然后就转化为了前一题,即询问 gy 级儿子有多少个。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;
using PII = pair<int, int>;

const int N = 100010;

struct Edge {
	int to, next;
}e[N * 2];

int head[N], idx;

void add(int a, int b) {
	idx++, e[idx].to = b, e[idx].next = head[a], head[a] = idx;
}

struct SegmentTree {
	int l, r;
	int s;
}tr[N * 20];

int root[N], tot;

void modify(int& cur, int l, int r, int x, int v) {
	if (!cur) cur = ++tot;
	if (l == r) {
		tr[cur].s += v;
		return;
	}
	
	int mid = (l + r - 1) / 2;
	
	if (x <= mid) modify(tr[cur].l, l, mid, x, v);
	else modify(tr[cur].r, mid + 1, r, x, v);
}

int n, m;
vector<PII> query[N];
int ans[N];

int merge(int cur1, int cur2, int l, int r) {
	if ((!cur1) || (!cur2)) return cur1 | cur2;
	if (l == r) {
		tr[cur1].s += tr[cur2].s;
		return cur1;
	}
	
	int mid = (l + r - 1) / 2;
	tr[cur1].l = merge(tr[cur1].l, tr[cur2].l, l, mid);
	tr[cur1].r = merge(tr[cur1].r, tr[cur2].r, mid + 1, r);
	return cur1;
}

int ask(int cur, int l, int r, int x) {
	if (!cur) return 0;
	if (l == r) {
		return tr[cur].s;
	}
	int mid = (l + r - 1) / 2;
	if (x <= mid) return ask(tr[cur].l, l, mid, x);
	else return ask(tr[cur].r, mid + 1, r, x);
}

int dep[N];
int f[N][20];

void dfs1(int u, int fa) {
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	modify(root[u], 1, N - 1, dep[u], 1);
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs1(to, u);
	}
}

void initf() {
	for (int j = 1; j < 20; j++) {
		for (int i = 1; i <= n; i++) {
			f[i][j] = f[f[i][j - 1]][j - 1];
		}
	}
}

void dfs2(int u, int fa) {
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs2(to, u);
		root[u] = merge(root[u], root[to], 1, N - 1);
	}
	for (int i = 0; i < query[u].size(); i++) {
		int d = query[u][i].first, id = query[u][i].second;
		ans[id] = ask(root[u], 1, N - 1, dep[u] + d);
	}
}

int getfa(int cur, int step_fa) {
	for (int i = 19; i >= 0; i--) {
		if (step_fa >> i & 1) {
			cur = f[cur][i];
		}
	}
	return cur;
}

vector<int> treeroot;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int fa;
		cin >> fa;
		if (fa == 0) {
			treeroot.push_back(i);
			continue;
		}
		add(fa, i);
		add(i, fa);
	}
	for (auto& x : treeroot) dfs1(x, 0);
	initf();
	cin >> m;
	for (int i = 1; i <= m; i++) {
		int x, y;
		cin >> x >> y;
		int g = getfa(x, y);
		query[g].push_back({y, i});
	}
	for (auto& x : treeroot) dfs2(x, 0);
	for (int i = 1; i <= m; i++) {
		if (ans[i]) cout << ans[i] - 1;
		else cout << 0;
		cout << ' ';
	}
	return 0;
}

CF490F Treeland Tour#

来自洛谷 CF490F的翻译:给出一棵带点权树,求树上最长上升子序列的长度。

考虑先计算出子树的所有信息,然后再合并到父亲节点。

f[i] 表示以树上节点 i 结尾的 LIS(最长上升子序列) 的长度。

g[i] 表示以树上节点 i 开头的 LDS(最长下降子序列) 的长度。

情况1:对于以 cur 为根节点的子树,经过 cur 的 LIS 的长度。

处理方法:

暴力:暴力枚举树上的每一个点进行转移。

每个树上节点以 a[i] 为值域,以 f[cur] 为元素维护一棵线段树。

最后找一个比 a[cur] 小的 a[k]f[a[k]] 是最大的元素进行转移,即 f[cur]=f[a[k]]+1

同时,当线段树合并时,也可以进行合并:

image

是为了处理

image

的情况。

情况2:对于以 cur 为根节点的子树,不经过 cur 的 LIS 长度为所有子节点的 LIS 取最值。

代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 6010, V = 1000000;

struct Edge {
	int to, next;
}e[N * 2];

int head[N], idx;

void add(int a, int b) {
	idx++;
	e[idx].to = b;
	e[idx].next = head[a];
	head[a] = idx;
}

int n;
int a[N], ans;

struct SegmentTree {
	int l, r;
	int lis;
	int lds;
}tr[N * 25];

int root[N], tot;

void pushup(int u) {
	tr[u].lis = max(tr[tr[u].l].lis, tr[tr[u].r].lis);
	tr[u].lds = max(tr[tr[u].l].lds, tr[tr[u].r].lds);
}

void modify(int& u, int l, int r, int x, int v1, int v2) {
	if (!u) u = ++tot;

	if (l == r) {
		tr[u].lis = max(tr[u].lis, v1);
		tr[u].lds = max(tr[u].lds, v2);
		return;
	}

	int mid = l + r >> 1;
	if (x <= mid) modify(tr[u].l, l, mid, x, v1, v2);
	else modify(tr[u].r, mid + 1, r, x, v1, v2);

	pushup(u);
}

int query(int u, int l, int r, int _left, int _right, int type) {
	if (!u) return 0;

	if (_left <= l && r <= _right) {
		if (type == 1) return tr[u].lis;
		else return tr[u].lds;
	}

	int res = 0;
	int mid = l + r >> 1;
	if (_left <= mid) res = max(res, query(tr[u].l, l, mid, _left, _right, type));
	if (_right > mid) res = max(res, query(tr[u].r, mid + 1, r, _left, _right, type));
	return res;
}

int merge(int cur1, int cur2, int l, int r) {
	if ((!cur1) || (!cur2)) return cur1 | cur2;

	int mid = l + r >> 1;

	tr[cur1].lis = max(tr[cur1].lis, tr[cur2].lis);
	tr[cur1].lds = max(tr[cur1].lds, tr[cur2].lds);

	ans = max(ans, max(tr[tr[cur1].l].lis + tr[tr[cur2].r].lds, tr[tr[cur1].r].lds + tr[tr[cur2].l].lis));
	tr[cur1].l = merge(tr[cur1].l, tr[cur2].l, l, mid);
	tr[cur1].r = merge(tr[cur1].r, tr[cur2].r, mid + 1, r);
	return cur1;
}

void dfs(int u, int fa) {
	int maxf = 0, maxxf = 0;
	int maxg = 0, maxxg = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		dfs(to, u);
		int x = query(root[to], 1, V, 1, a[u] - 1, 1);
		int y = query(root[to], 1, V, a[u] + 1, V, 2);
		ans = max(ans, maxf + y + 1);
		ans = max(ans, x + maxg + 1);
		maxf = max(maxf, x);
		maxg = max(maxg, y);
		root[u] = merge(root[u], root[to], 1, V);
	}
	modify(root[u], 1, V, a[u], maxf + 1, maxg + 1);
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		modify(root[i], 1, V, a[i], 1, 1);
	}

	for (int i = 1; i < n; i++) {
		int x, y;
		cin >> x >> y;
		add(x, y);
		add(y, x);
	}
	dfs(1, 0);
	cout << ans << '\n';
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	
	solve();
	return 0;
}
posted @   SunnyYuan  阅读(7)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示