前置知識:線段樹分治。
題意
給定 \(n\) 個節點的樹,每個節點有一個二元組集合 \(S_i\)。
這個集合有一個限制:\(S_i\) 一定是 \(S_{fa_i}\) 中刪除一個二元組或者加一個二元組,並且加進來的二元組互不相同。
現在有 \(m\) 個詢問,每個詢問給出 \(k, h\) 表示查詢 \(\min\limits_{(x, c) \in S_k}\{(x - h)^2 + c\}\)。
題解
好題啊,首先可以將式子拆成好算的形式:
\[\begin{aligned}
&(x - h)^2 + cc\\
=&x^2 + h^2 - 2xh + c\\
=&h^2 + [-2xh + (x^2 + c)]
\end{aligned}
\]
這相當於求 \(S_k\) 中所有斜率為 \(-2x\),截距為 \(x^2 + c\) 的直線在 \(x = h\) 時縱座標的最大值。而這是李超線段樹的經典應用。於是考慮先把詢問離線下來,接著 dfs 整棵樹並更新這個 \(S\),再對於這個節點的每個詢問一一回答即可。
可是李超線段樹是不支援刪除的,只支援撤銷(實際上大部分資料結構都支援撤銷)。於是考慮把刪除轉化為撤銷,故使用線段樹分治。所以新的問題又出現了,如何找一個直線在哪些節點上出現了。觀察一下可以發現,對於所有包含同一條直線的點,這些點在樹上一定是一個連通塊,因為題目中保證了二元組互不相同。
所以如果將樹的 dfn 序處理出來,包含一條直線的所有點的 dfn 序集合,一定是由若干區間組成的,因為每有一個刪除操作,都相當於是在這條直線的區間中挖掉一部分。假設區間數量為 \(t\),易證 \(\sum t\) 是 \(O(n)\) 的。這樣就可以使用線段樹分治了,時間複雜度 \(O(n \log^2 n)\)。
程式碼
code:
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 5E5 + 5;
int n, m, q;
vector <int> G[N];
struct line {
i64 k, b;
void gen(int x, int c) {
k = -2LL * x;
b = 1LL * x * x + c;
}
} li[N];
struct opr {int opt, id;} a[N];
int dl[N], dr[N], tot; vector <pair <int, int>> ed[N];
pair <int, int> be[N];
void dfs(int x, int fa) {
dl[x] = ++tot;
for (auto v : G[x]) {
if (v == fa) continue;
dfs(v, x);
} dr[x] = tot;
if (a[x].opt == 1) ed[a[x].id].emplace_back(dl[x], dr[x]);
else be[a[x].id] = make_pair(dl[x], dr[x]);
}
i64 Ans[N];
vector <int> v[N << 2];
vector <pair <int, int>> qs[N];
stack <pair <int, int>> st;
struct lc_segt {
int root, tot;
struct node {int ls, rs, s;} t[N << 5];
i64 calc(int u, int v) {return li[u].k * v + li[u].b;}
void upd(int &x, int l, int r, int u) {
if (!x) x = ++tot;
if (t[x].s == 0) {
st.emplace(x, 0);
t[x].s = u;
return ;
} int mid = (l + r) >> 1, &v = t[x].s;
if (calc(u, mid) < calc(v, mid)) {
st.emplace(x, v);
swap(u, v);
}
if (l == r) return ;
if (calc(u, l) < calc(v, l)) upd(t[x].ls, l, mid, u);
if (calc(u, r) < calc(v, r)) upd(t[x].rs, mid + 1, r, u);
}
i64 query(int x, int l, int r, int p) {
if (!x || !t[x].s) return 2E18;
i64 now = calc(t[x].s, p); int mid = (l + r) >> 1;
if (l == r) return now;
if (p <= mid) return min(now, query(t[x].ls, l, mid, p));
else return min(now, query(t[x].rs, mid + 1, r, p));
}
void push(int id) {upd(root, -1E6, 1E6, id);}
i64 query(int p) {return query(root, -1E6, 1E6, p);}
void cl(int to) {
while ((int)st.size() > to) {
auto [x, y] = st.top(); st.pop();
t[x].s = y;
}
}
} t;
void ins(int x, int l, int r, int L, int R, int id) {
if (L > R) return ;
if (l >= L && r <= R) {
v[x].emplace_back(id);
return ;
} int mid = (l + r) >> 1;
if (L <= mid) ins(x << 1, l, mid, L, R, id);
if (R > mid) ins(x << 1 | 1, mid + 1, r, L, R, id);
}
void solve(int x, int l, int r) {
int now = st.size();
for (auto id : v[x]) t.push(id);
int mid = (l + r) >> 1;
if (l == r) {
for (auto [p, id] : qs[l]) {
i64 tmp = t.query(p);
Ans[id] = tmp + 1LL * p * p;
}
} else solve(x << 1, l, mid), solve(x << 1 | 1, mid + 1, r);
t.cl(now);
}
signed main(void) {
ios :: sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int c0;
cin >> n >> q >> c0; li[1].gen(0, c0); a[1].opt = 0; a[1].id = 1;
for (int i = 2; i <= n; ++i) {
int opt, fr, id; cin >> opt >> fr >> id;
++fr, ++id; G[fr].emplace_back(i);
if (opt == 0) {int x, c; cin >> x >> c >> c >> c; li[id].gen(x, c);}
a[i].opt = opt; a[i].id = id;
} dfs(1, 0);
for (int i = 1; i <= n; ++i) if (be[i].first) {
sort(ed[i].begin(), ed[i].end());
int nl = be[i].first;
for (auto [l, r] : ed[i]) {
ins(1, 1, n, nl, l - 1, i);
nl = r + 1;
} ins(1, 1, n, nl, be[i].second, i);
}
for (int i = 1; i <= q; ++i) {
int s, x; cin >> s >> x; ++s;
qs[dl[s]].emplace_back(x, i);
} solve(1, 1, n);
for (int i = 1; i <= q; ++i) cout << Ans[i] << '\n';
}