P8990 [北大集训 2021] 小明的树 题解

P8990 [北大集训 2021] 小明的树 题解

首先刻画 “美丽”,考虑灭点,由于 1 始终灭,所以形如一个包含 1 的连通块,灭点连通块数量要为 1。

因为要刻画连通块数量,考虑点减边容斥,即连通块个数是灭点个数减 “灭-灭” 边个数,而贡献就是 “灭-亮” 边个数。

维护每一个时刻连通块数量和 “灭-亮” 边个数,答案就是连通块数量为 1 的那些时刻的 “灭-亮” 边个数之和。

如果删除一条边,需要去掉它的贡献,它的贡献形如两个区间,用线段树维护,因为连通块数量不可能 \(< 1\),所以只需要维护最小值的贡献和。

#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
#define int long long
using namespace std;
const int N = 5e5 + 10, INF = 1e18;

int n, m, a[N], b[N], u[N], v[N];
vector<int> g[N];
// A 最小值,最小值个数,B 和,A 最小值的 B 的和 
struct qwq {
    int mn, cnt, sum, taga, tagb;
    qwq operator + (const qwq &W) const {
        qwq tmp;
        tmp.mn = mn, tmp.cnt = cnt, tmp.sum = sum;
        if(tmp.mn > W.mn) tmp.mn = W.mn, tmp.cnt = W.cnt, tmp.sum = W.sum;
        else if(tmp.mn == W.mn) tmp.cnt += W.cnt, tmp.sum += W.sum;
        return tmp;
    }
} dat[N << 2];
void align(int u, int v, int op) {
    if(op == 0) dat[u].mn += v, dat[u].taga += v;
    else dat[u].sum += dat[u].cnt * v, dat[u].tagb += v;
}
void up(int u) {
    dat[u] = dat[u << 1] + dat[u << 1 | 1];
}
#define mid ((l + r) >> 1)
void build(int u, int l, int r) {
    if(l == r) return dat[u].mn = l, dat[u].cnt = 1, void();
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r), up(u);
}
void down(int u) {
    if(dat[u].taga) align(u << 1, dat[u].taga, 0), align(u << 1 | 1, dat[u].taga, 0), dat[u].taga = 0;
    if(dat[u].tagb) align(u << 1, dat[u].tagb, 1), align(u << 1 | 1, dat[u].tagb, 1), dat[u].tagb = 0;
}
void update(int u, int l, int r, int ql, int qr, int v, int op) {
    if(ql <= l && qr >= r) return align(u, v, op);
    down(u);
    if(ql <= mid) update(u << 1, l, mid, ql, qr, v, op);
    if(qr > mid) update(u << 1 | 1, mid + 1, r, ql, qr, v, op);
    up(u);
}
void print(int u, int l, int r) {
    if(l == r) {
        cout << dat[u].sum << ' ';
        return ;
    }
    down(u);
    print(u << 1, l, mid);
    print(u << 1 | 1, mid + 1, r);
}
void calc(int u, int v, int op) {
    int x = min(b[u], b[v]), y = max(b[u], b[v]);
    if(x < y) update(1, 1, n, x, y - 1, op, 1);
    if(y <= n) update(1, 1, n, y, n, -op, 0);
}
int calc() {
    auto t = dat[1];
    if(t.mn == 1) return t.sum;
    return 0;
}
signed main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> m;
    for(int i = 1, a, b; i < n; i ++) {
        cin >> a >> b;
        u[i] = a, v[i] = b;
    }
    b[1] = 1;
    for(int i = 2; i <= n; i ++) cin >> a[n - i + 2];
    for(int i = 2; i <= n; i ++) b[a[i]] = i;
    build(1, 1, n);
    for(int i = 1; i < n; i ++) calc(u[i], v[i], 1);
    cout << calc() << '\n';
    for(int i = 1, a, b, c, d; i <= m; i ++) {
        cin >> a >> b >> c >> d;
        calc(a, b, -1), calc(c, d, 1); 
        cout << calc() << '\n';
    }
    
    return 0;
}

应该把所有的观察写到纸上!防止忘记。

posted @ 2025-11-05 23:02  MoyouSayuki  阅读(4)  评论(0)    收藏  举报
:name :name