题解:P14380 【MX-S9-T3】「LAOI-16」天外来物

· · 题解

小清新数据结构题。

首先想到类似支配对的东西:称区间 [l,r] 是极小的,如果 f(l,r)\neq f(l+1,r) 且 f(l,r)\neq f(l,r-1)。

那么答案就是 [L,R] 内的极小子区间 [l,r] 的数量。

极小区间的数量是 \mathcal O(N^2) 的(星形图),意味着必须要找一些好的充要条件刻画之。

不难看出,[l,r] 是极小的,当且仅当 l,r 在 f(l,r) 上都是叶子,并且「l 成为叶子」是对 r 单调的,r 同理。

因此设 \text{pre}_i 表示最小的 x\le i,使得 i 在 f(x,i) 上是叶子,\text{suf}_i 同理。

那么 [l,r] 极小等价于 \text{pre}_r\le l\land\text{suf}_l\ge r。

考虑如何求 \text{pre}_i。i 不是叶子,意味着 f(x,i) 和删去 i 得到的至少 2 个连通块有交。

因此 $i$ 从小到大扫描线,用线段树维护单点修改、区间最值,查询时获取每个子树(含父亲方向)内的最大值,取所有子树的次大值即可。$\text{suf}_i$ 同理。 考虑如何回答询问。$l$ 从右往左扫描线,如果不考虑 $\text{pre}_r\ge l$ 的限制,就是 $[l,\text{suf}_l]$ 内的 $r$ 加 $1$。查询区间和。 $\text{pre}_r$ 的限制意味着 $r$ 会逐渐失效,再维护 $d_r$ 表示 $r$ 是否仍有效,在 $l$ 处把 $\text{pre}_r<l$ 的 $r$ 的 $d_r$ 置零,线段树维护的改为区间加 $d_r$ 即可。 时间复杂度 $\mathcal O(n\log n)$。 ```cpp #include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5e5 + 5, S = 1 << 20; char buf[S], *p1, *p2; #define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, S, stdin), p1 == p2) ? EOF : *p1++) inline void rd(int &x) { x = 0; char c = 0; while (c < '0' || c > '9') c = gc; while (c >= '0' && c <= '9') x = x * 10 - '0' + c, c = gc; } int n, q, fa[N], tot, dfn[N], mxd[N], pre[N], suf[N], qr[N]; ll ans[N]; basic_string<int> G[N], qv[N], dl[N]; void dfs(int u) { dfn[u] = ++tot; for (int v : G[u]) if (v ^ fa[u]) fa[v] = u, dfs(v); mxd[u] = tot; } #define lc (p << 1) #define rc (lc | 1) struct Seg_max { int op, t[N << 2]; void init(int p, int l, int r, int v) { t[p] = v; if (l == r) return; int mid = (l + r) >> 1; init(lc, l, mid, v), init(rc, mid + 1, r, v); } void upd(int p, int l, int r, int x, int v) { if (l == r) return void(t[p] = v); int mid = (l + r) >> 1; x <= mid ? upd(lc, l, mid, x, v) : upd(rc, mid + 1, r, x, v); t[p] = op > 0 ? max(t[lc], t[rc]) : min(t[lc], t[rc]); } int qry(int p, int l, int r, int L, int R) { if (L <= l && r <= R) return t[p]; int mid = (l + r) >> 1; if (R <= mid) return qry(lc, l, mid, L, R); if (L > mid) return qry(rc, mid + 1, r, L, R); int ql = qry(lc, l, mid, L, R), qr = qry(rc, mid + 1, r, L, R); return op > 0 ? max(ql, qr) : min(ql, qr); } } t; struct Seg_add { ll t[N << 2]; int d[N << 2], tg[N << 2]; void init(int p, int l, int r) { if (l == r) return void(d[p] = 1); int mid = (l + r) >> 1; init(lc, l, mid), init(rc, mid + 1, r), d[p] = d[lc] + d[rc]; } void mdf(int p, ll x) { t[p] += d[p] * x, tg[p] += x; } void psd(int p) { if (tg[p]) mdf(lc, tg[p]), mdf(rc, tg[p]), tg[p] = 0; } void del(int p, int l, int r, int x) { if (l == r) return void(d[p] = 0); int mid = (l + r) >> 1; psd(p); x <= mid ? del(lc, l, mid, x) : del(rc, mid + 1, r, x), d[p] = d[lc] + d[rc]; } void upd(int p, int l, int r, int L, int R, int x) { if (L <= l && r <= R) return mdf(p, x); int mid = (l + r) >> 1; psd(p); if (L <= mid) upd(lc, l, mid, L, R, x); if (R > mid) upd(rc, mid + 1, r, L, R, x); t[p] = t[lc] + t[rc]; } ll qry(int p, int l, int r, int L, int R) { if (L <= l && r <= R) return t[p]; int mid = (l + r) >> 1; ll ret = 0; psd(p); if (L <= mid) ret += qry(lc, l, mid, L, R); if (R > mid) ret += qry(rc, mid + 1, r, L, R); return ret; } } s; int main() { // freopen("tree.in", "r", stdin), freopen("tree.out", "w", stdout); rd(n), rd(q); for (int i = 1, u, v; i < n; i++) rd(u), rd(v), G[u] += v, G[v] += u; dfs(1), t.op = 1; for (int i = 1; i <= n; i++) { t.upd(1, 1, n, dfn[i], i); basic_string<int> val; for (int v : G[i]) if (v ^ fa[i]) val += t.qry(1, 1, n, dfn[v], mxd[v]); if (i != 1) val += max (dfn[i] > 1 ? t.qry(1, 1, n, 1, dfn[i] - 1) : 0, mxd[i] < n ? t.qry(1, 1, n, mxd[i] + 1, n) : 0); int mx = -1, mx2 = -1; for (int x : val) if (x > mx) mx2 = mx, mx = x; else if (x > mx2) mx2 = x; pre[i] = mx2 > 0 ? mx2 + 1 : 1; } t.op = -1, t.init(1, 1, n, n + 1); for (int i = n; i >= 1; i--) { t.upd(1, 1, n, dfn[i], i); basic_string<int> val; for (int v : G[i]) if (v ^ fa[i]) val += t.qry(1, 1, n, dfn[v], mxd[v]); if (i != 1) val += min (dfn[i] > 1 ? t.qry(1, 1, n, 1, dfn[i] - 1) : n + 1, mxd[i] < n ? t.qry(1, 1, n, mxd[i] + 1, n) : n + 1); int mn = n + 2, mn2 = n + 2; for (int x : val) if (x < mn) mn2 = mn, mn = x; else if (x < mn2) mn2 = x; suf[i] = mn2 <= n ? mn2 - 1 : n; } // for (int i = 1; i <= n; i++) cerr << pre[i] << ' ' << suf[i] << '\n'; // #define BRUTE #ifdef BRUTE while (q--) { int l, r; ll ans = 0; cin >> l >> r; for (int i = l; i <= r; i++) for (int j = i; j <= r; j++) ans += (suf[i] >= j && pre[j] <= i); cout << ans << '\n'; } #else for (int i = 1, l; i <= q; i++) rd(l), rd(qr[i]), qv[l] += i; for (int i = 1; i <= n; i++) dl[pre[i] - 1] += i; s.init(1, 1, n); for (int l = n; l >= 1; l--) { for (int r : dl[l]) s.del(1, 1, n, r); s.upd(1, 1, n, l, suf[l], 1); for (int i : qv[l]) ans[i] = s.qry(1, 1, n, l, qr[i]); } for (int i = 1; i <= q; i++) cout << ans[i] << '\n'; #endif } /* 考虑类似支配对的东西。但这里不保证数量(星形图) [l, r] 极小,意味着 l, r 在斯坦纳树上都是叶子 设 pre[i] 表示最小的 x 使得 i 在 [x, i] 的斯坦纳树上是叶子,suf[i] 同理 那么 [l, r] 极小等价于 pre[r] <= l && suf[l] >= r 怎么求 pre?考虑去掉 i 之后的每个连通块,分别求出最大的 x,使得 [x, i] 的斯坦纳树有该连通块内的点 然后取 次大值 + 1 是不是就对了,suf 同理,但是具体怎么算呢? i 从小到大扫,在 dfn 序上搞,就是查区间最大值? 怎么回答询问?区间二维偏序数是什么能 polylog 的东西吗。 l 从大到小扫,一个 r 会在 pre[r] - 1 处变得不合法,相当于每次加入 / 减去一些 r 的贡献 再维护一个 d,然后就是区间 w += d,查询区间和,线段树维护 */ ```