注意到直接根据题目的条件判断树是否美丽并不容易。考虑未被点亮的点,可以发现一棵树是美丽的当且仅当未被点亮的点形成一个连通块。
有一个结论是,对于一个森林,点数减边数等于连通块的个数。\((*)\)
因此树是美丽的当且仅当 “未被点亮的节点的个数”减去“两端都未被点亮的边的个数” \(=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;
}
标签:点亮,int,Luogu,sum,mn,nd,add,P8890
From: https://www.cnblogs.com/untitled0/p/luogu-P8890.html