Luogu P8990

题面

注意到直接根据题目的条件判断树是否美丽并不容易。考虑未被点亮的点,可以发现一棵树是美丽的当且仅当未被点亮的点形成一个连通块

有一个结论是,对于一个森林,点数减边数等于连通块的个数。\((*)\)

因此树是美丽的当且仅当 “未被点亮的节点的个数”减去“两端都未被点亮的边的个数” \(=1\)。令 \(P\) 表示这个值,当且仅当 \(P=1\) 时树是美丽的。

同时根据 \(*\) 结论,树上被点亮的连通块的个数就是 “被点亮的节点的个数”减去“两端都被点亮的边的个数”。设这个值为 \(Q\)

如果我们按顺序执行点亮操作,那么每个时刻的 \(P\)\(Q\) 会分别形成一个序列。更准确地说,令 \(p_i\) 表示点亮完第 \(a_i\) 个点后的 \(P\) 值,\(q_i\) 表示点亮完第 \(a_i\) 个点后的 \(Q\) 值,我们就得到了 \(p\)\(q\) 两个序列,答案即为 \(\sum\limits_{i=1}^{n-1}q_i\times[p_i=1]\)。(\([X]\)\(X\) 为真时 \(=1\),否则 \(=0\)

接下来考虑 link-cut 操作,先考虑断掉一条边 \((u,v)\) 时对 \(p\)\(q\) 的影响:由于一个节点是否点亮只决定于 \(i\),所以边的情况不会影响点的情况。记 \(t_i\) 表示点 \(i\) 被点亮的时刻,则 \((u,v)\) 脱离“两端都未被点亮”状态的时刻就是 \(\min(t_u, t_v)\),在这之前它都对 \(p\) 有贡献,因此删去它后会使 \(p\)\([1, \min(t_u, t_v) - 1]\) 区间每个数 \(+1\)。它变成“两端都被点亮”的状态的时刻是 \(\max(t_u, t_v)\),在这之后它都对 \(q\) 有贡献,因此删去它后会使 \(q\)\([\max(t_u, t_v),n-1]\) 区间每个数 \(+1\)

同理,如果连上一条边 \((x,y)\),则它会使 \(p\)\([1,\min(t_x,t_y)-1]\) 区间 \(-1\)\(q\)\([\max(t_x,t_y),n-1]\) 区间 \(-1\)

于是转化成了一个线段树问题。具体地,对于线段树每个节点 \([l, r]\),维护 \(mn=\min\limits_{i=l}^rp_i\)\(cnt = \sum\limits_{i=l}^r[p_i=mn]\)\(sum = \sum\limits_{i=l}^rq_i\times[p_i=mn]\) 以及加法标记即可。

时间复杂度 \(O((n+m)\log n)\)

#include<bits/stdc++.h>
#define endl '\n'
#define rep(i, s, e) for(int i = s, i##E = e; i <= i##E; ++i)
#define per(i, s, e) for(int i = s, i##E = e; i >= i##E; --i)
#define F first
#define S second
#define int ll
#define gmin(x, y) (x = min(x, y))
#define gmax(x, y) (x = max(x, y))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double f128;
typedef pair<int, int> pii;
constexpr int N = 5e5 + 5;
int n, m, a[N], t[N];
struct edge {
    int u, v;
} e[N];
struct node {
    int mn, sum, cnt, t1, t2;
} nd[N * 4];
#define ls (p << 1)
#define rs (p << 1 | 1)
void upd(int p, int v, int w) {
    nd[p].mn += v;
    nd[p].t1 += v;
    nd[p].sum += nd[p].cnt * w;
    nd[p].t2 += w;
}
void pushdown(int p) {
    if(nd[p].t1 || nd[p].t2) {
        upd(ls, nd[p].t1, nd[p].t2);
        upd(rs, nd[p].t1, nd[p].t2);
        nd[p].t1 = nd[p].t2 = 0;
    }
}
void pushup(int p) {
    if(nd[ls].mn < nd[rs].mn) nd[p].mn = nd[ls].mn, nd[p].sum = nd[ls].sum, nd[p].cnt = nd[ls].cnt;
    else if(nd[ls].mn > nd[rs].mn) nd[p].mn = nd[rs].mn, nd[p].sum = nd[rs].sum, nd[p].cnt = nd[rs].cnt;
    else nd[p].mn = nd[ls].mn, nd[p].sum = nd[ls].sum + nd[rs].sum, nd[p].cnt = nd[ls].cnt + nd[rs].cnt;
}
void build(int p, int l, int r) {
    if(l == r) {
        nd[p].mn = n - l;
        nd[p].sum = l;
        nd[p].cnt = 1;
        return;
    }
    int mid = (l + r) / 2;
    build(ls, l, mid), build(rs, mid + 1, r);
    pushup(p);
}
void add(int p, int l, int r, int ql, int qr, int v, int w) {
    if(l > qr || r < ql) return;
    if(ql <= l && qr >= r) return upd(p, v, w);
    pushdown(p);
    int mid = (l + r) / 2;
    add(ls, l, mid, ql, qr, v, w);
    add(rs, mid + 1, r, ql, qr, v, w);
    pushup(p);
}
signed main() {
#ifdef ONLINE_JUDGE
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
#endif
    cin >> n >> m;
    build(1, 1, n - 1);
    rep(i, 1, n - 1) cin >> e[i].u >> e[i].v;
    rep(i, 1, n - 1) cin >> a[i], t[a[i]] = i;
    t[1] = n;
    rep(i, 1, n - 1) {
        int u = e[i].u, v = e[i].v;
        add(1, 1, n - 1, 1, min(t[u], t[v]) - 1, -1, 0);
        add(1, 1, n - 1, max(t[u], t[v]), n - 1, 0, -1);
    }
    cout << (nd[1].mn == 1 ? nd[1].sum : 0) << endl;
    while(m--) {
        int u, v, x, y;
        cin >> u >> v >> x >> y;
        add(1, 1, n - 1, 1, min(t[u], t[v]) - 1, 1, 0);
        add(1, 1, n - 1, max(t[u], t[v]), n - 1, 0, 1);
        add(1, 1, n - 1, 1, min(t[x], t[y]) - 1, -1, 0);
        add(1, 1, n - 1, max(t[x], t[y]), n - 1, 0, -1);
        cout << (nd[1].mn == 1 ? nd[1].sum : 0) << endl;
    }
    return 0;
}
posted @ 2023-05-08 14:58  untitled0  阅读(20)  评论(0)    收藏  举报