樹分治
點分治
本質上是一種 思想,用於統計 帶權樹 上 多條路徑資訊 的 方法之一
一般來講,點分治的基本思路 如下
-
將需要統計的 路徑 按 是否經過當前根節點 分成兩類
-
用 某種方法 統計 經過根節點 的部分(由於已經有 經過根節點 這個限制了,簡單一些)
-
不經過根節點 則 整個路徑都在 當前根節點一個兒子的子樹內
遞迴統計 那個兒子的子樹 即可
這樣我們可以把 對於整棵樹的特殊路徑統計 轉化為 對於經過某個點的特殊路徑統計
有些題(\(P5563 ~ ...\))還需要 對端點為當前根 的路徑 單獨討論,但也是簡單的
看上去就 變簡單了很多
需要注意的是,這種結構的複雜度並不固定,取決於具體實現
但 遞迴次數 是可以 有保證的,注意到我們 每次的根 不要直接選取 那個兒子
而是 選取那棵子樹的重心,這樣 遞迴一次子樹大小至少減半,至多遞迴 \(O(\log N)\) 次
點分治結構常數巨大,能 一次做掉所有詢問 就不要 每個詢問做一次(即使 複雜度一樣)
Luogu P3806 【模板】點分治 1
\(Double ~ EXP\) CF161D Distance in Tree
給定一棵 帶權樹,詢問 樹上是否存在 距離為 \(K\) 的點對
即統計 長為 \(K\) 的路徑
套路分類,於是我們 只需要思考 經過根的長為 \(K\) 的路徑是否存在
顯然 路徑兩端不能在同一個兒子的子樹內,故我們 遍歷當前根的兒子
計算 當前兒子子樹中 每個點到當前根的距離 \(Dis_i\) 並存下來
我們用 bool[]
記錄下(在這個兒子之前的兒子子樹內)到當前根距離為 \(x\) 的點是否存在
由於 \(Dis_{max} = 10 ^ 8\),這裡
bool[]
可能會 \(MLE\),可以使用std::bitset
於是現在遍歷 \(Dis_i\),查詢 到根距離為 \(K - Dis_i\) 的點 是否存在 即可
遍歷完之後把 令所有 bool[Dis[i]] = 1
並清空 Dis
,繼續計算 下一個子樹
遍歷完 所有子樹 即可得到所有 經過當前根的長為 \(K\) 的路徑
往 每個兒子的子樹內遞迴 即可,注意選取 當前根 為 當前子樹重心
注意這裡 我們把所有詢問離線下來,每次查詢時 遍歷所有詢問來統計答案
雖然這和 線上詢問,每詢問一次就做一次 點分治 的時間複雜度均為 \(O(NM \log N)\)
但是 由於點分治結構常數巨大,故 線上詢問 會獲得 \(12 \sim 14\) 倍 左右的 小 常 數
程式碼還好,三個 \(DFS\),分別計算 重心,距離,答案
#include <bits/stdc++.h>
const int MAXN = 10005;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN], Ans[MAXN];
int Siz[MAXN], Max[MAXN];
int N, M, K, Cnt, Now, rt;
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
int P[MAXN], Q[MAXN];
inline void DFS2 (const int x, const int f, const int dis) {
P[++ Cnt] = dis;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dis + E[i].w);
}
bitset <1 << 27> Ext;
vector <int> S;
inline void DFS3 (const int x, const int f) {
Ext[0] = 1, S.push_back (0), Vis[x] = 1;
for (int i = H[x]; i; i = E[i].nxt) {
if (Vis[E[i].to] || E[i].to == f) continue ;
Cnt = 0, DFS2 (E[i].to, x, E[i].w);
for (int k = 1; k <= Cnt; ++ k)
for (int j = 1; j <= M; ++ j)
if (Q[j] >= P[k]) Ans[j] |= Ext[Q[j] - P[k]];
for (int k = 1; k <= Cnt; ++ k) Ext[P[k]] = 1, S.push_back (P[k]);
}
while (!S.empty ()) Ext[S.back ()] = 0, S.pop_back ();
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int u, v, w;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> M;
for (int i = 2; i <= N; ++ i)
cin >> u >> v >> w, Add_Edge (u, v, w);
for (int i = 1; i <= M; ++ i) cin >> Q[i];
Now = N, DFS1 (1, 0), DFS3 (rt, 0);
for (int i = 1; i <= M; ++ i)
cout << (Ans[i] ? "AYE" : "NAY") << '\n';
return 0;
}
Luogu P2634 [國家集訓隊] 聰聰可可
記錄 距離 \(\bmod 3\) 餘數為 \(0, 1, 2\) 的點的個數
然後和 板子題 一樣做就行
#include <bits/stdc++.h>
const int MAXN = 100005;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN], Tmp[5];
int N, Cnt, Now, rt;
long long Ans, Gcd;
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
int P[MAXN];
inline void DFS2 (const int x, const int f, const int dis) {
P[++ Cnt] = dis % 3;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dis + E[i].w);
}
inline void DFS3 (const int x, const int f) {
Vis[x] = 1, Tmp[0] = 1;
for (int i = H[x]; i; i = E[i].nxt) {
if (Vis[E[i].to] || E[i].to == f) continue ;
Cnt = 0, DFS2 (E[i].to, x, E[i].w);
for (int i = 1; i <= Cnt; ++ i) Ans += Tmp[(3 - P[i]) % 3];
for (int i = 1; i <= Cnt; ++ i) Tmp[P[i]] ++ ;
}
Tmp[0] = Tmp[1] = Tmp[2] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int u, v, w;
int main () {
cin >> N;
for (int i = 2; i <= N; ++ i)
cin >> u >> v >> w, Add_Edge (u, v, w);
Now = N, DFS1 (1, 0), DFS3 (rt, 0);
Ans = Ans * 2 + N, Gcd = __gcd (1ll * N * N, Ans);
cout << Ans / Gcd << '/' << 1ll * N * N / Gcd << '\n';
return 0;
}
Luogu P4149 [IOI2011] Race
這就比較板
給定 帶權樹,求 權值和等於 \(k\) 的 最小邊數路徑
顯然 我們只需要管 路徑權值和小於 \(k\) 的部分,由於 \(k \le 10 ^ 6\)
直接存所有 長度小於 \(k\) 的路徑的 最小深度 即可
遍歷兒子子樹時,對這樣的路徑 更新最小深度
每遍歷完一棵子樹,嘗試與 前面兒子子樹 內的答案 合併
即若這棵子樹中存在路徑 \(Len = x < k\),那麼找到前面記錄下來的長度為 \(k - x\) 的 最小深度
兩個加起來,與 當前最小深度 取 最小值,注意特判 端點在當前根 的情況即可
#include <bits/stdc++.h>
const int MAXN = 200005;
const int MAXV = 1000005;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN];
int N, K, Now, Cnt, rt;
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
int Tmp[MAXV];
int Ans = 1e9;
struct Node {
int dis, dep;
inline bool operator < (const Node &a) const {
return dis < a.dis;
}
} P[MAXN];
inline void DFS2 (const int x, const int f, const int dis, const int dep) {
P[++ Cnt] = {dis, dep};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dis + E[i].w, dep + 1);
}
vector <int> S;
inline void DFS3 (const int x, const int f) {
Vis[x] = 1, Tmp[0] = 0;
for (int i = H[x]; i; i = E[i].nxt) {
if (Vis[E[i].to] || E[i].to == f) continue ;
Cnt = 0, DFS2 (E[i].to, x, E[i].w, 1);
for (int i = 1; i <= Cnt; ++ i) {
if (P[i].dis > K) continue ;
else if (P[i].dis == K) Ans = min (Ans, P[i].dep);
else if (Tmp[K - P[i].dis]) Ans = min (Ans, P[i].dep + Tmp[K - P[i].dis]);
}
for (int i = 1; i <= Cnt; ++ i)
if (P[i].dis <= K)
S.push_back (P[i].dis), Tmp[P[i].dis] = Tmp[P[i].dis] ? min (Tmp[P[i].dis], P[i].dep) : P[i].dep;
}
while (!S.empty ()) Tmp[S.back ()] = 0, S.pop_back ();
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int u, v, w;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> K;
for (int i = 2; i <= N; ++ i)
cin >> u >> v >> w, ++ u, ++ v, Add_Edge (u, v, w);
Now = N, DFS1 (1, 0), DFS3 (rt, 0);
cout << (Ans == 1e9 ? -1 : Ans) << '\n';
return 0;
}
Luogu P4178 Tree
和上面的板子題 區別不大,從 統計 \(= K\) 的路徑 變成 統計 \(\le K\) 的路徑
同時 增大 \(N\),但 詢問變成單次
同樣 只需要考慮經過根的 \(\le K\) 的路徑條數
可以使用一個 std::set
,每次遍歷一棵子樹,統計 每個點到當前根距離 \(Dis_i\)
遍歷 \(Dis_i\) 並在 std::set
中 查詢 有多少小於等於 \(K - Dis_i\) 的點
遍歷完之後把當前所有 \(Dis\) 插入 std::set
即可
但是這樣常數比較大,考慮一種高明的做法
注意到
std::set
甚至不能 方便的查詢小於等於 \(x\) 的數的個數沒人想 手寫平衡樹 吧
我們把 當前根子樹內所有點到當前根距離 \(Dis_i\) 全部計算並存下來
同時需要記錄 每個點屬於哪個兒子
對這個東西 按 \(Dis\) 排序,然後使用 雙指標 \(L, R\) 維護
我們考慮 一條有一端點位於 \(L\) 的路徑,找到 \(R\) 使得 \(Dis_L + Dis_R \le K\)
則 \((L, R]\) 的 所有點(除了和 \(L\) 在 同一個兒子子樹內 的)均 滿足條件
顯然,隨著 \(L\) 變大(向右),\(R\) 單調不增,正確性存在
這時候開一個陣列 Tmp
記錄一下當前 \([L, R]\) 內 在某個兒子子樹內 的點有多少 就行
每次答案加上 R - L + 1 - Tmp[Son[L]]
即可
#include <bits/stdc++.h>
const int MAXN = 100005;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN], Tmp[MAXN];
int N, K, Now, Cnt, rt;
long long Ans;
struct Node {
int dis, top;
inline bool operator < (const Node &a) const {
return dis < a.dis;
}
} P[MAXN];
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] <= Max[rt] || !rt) rt = x;
}
inline void DFS2 (const int x, const int f, const int top, const int dis) {
P[++ Cnt] = {dis, top};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, top, dis + E[i].w);
}
inline void DFS3 (const int x, const int f) {
Cnt = 0, P[++ Cnt] = {0, 0}, Vis[x] = 1;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, E[i].to, E[i].w);
sort (P + 1, P + Cnt + 1);
for (int i = 1; i <= Cnt; ++ i) Tmp[P[i].top] ++ ;
int L = 1, R = Cnt;
while (L < R) {
while (P[L].dis + P[R].dis > K) Tmp[P[R --].top] -- ;
if (R < L) break ;
Ans += R - L - Tmp[P[L].top] + 1, Tmp[P[L ++].top] -- ;
}
for (int i = 1; i <= Cnt; ++ i) Tmp[P[i].top] = 0;
for(int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int u, v, w;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N;
for (int i = 2; i <= N; ++ i)
cin >> u >> v >> w, Add_Edge (u, v, w);
cin >> K;
Now = N, DFS1 (1, 0);
DFS3 (rt, 0);
cout << Ans << '\n';
return 0;
}
CF293E Close Vertices
\(P4178\) 的 加強版,對 邊數 和 距離 兩維做出了限制
二維偏序,考慮 一維排序,一維 樹狀陣列
然後應當是簡單的,就是把 \(P4178\) 統計答案的部分 Ans += R - L + 1
換成 Ans += Sum (K - P[L].dep)
,同時 樹狀陣列 中動態維護 \([L, R]\) 區間的 \(Dep\) 即可
顯然 \(Dep\) 值域比較小,適合插入 樹狀陣列,用 \(Dis\) 就比較壞
注意 去除同一子樹貢獻 時,由於 每個點有 兩維屬性 需要滿足
故不能用 \(P4178\) 的 記錄 \([L, R]\) 之間在當前子樹的點個數 這種方式
考慮樸素的方法,即 算完整個樹答案 然後減去 每個兒子子樹獨立的答案 即可
這題細節有點煩的
考慮由於 樹狀陣列 不能插 \(0\),為了插入 當前根
故可以把 所有點 \(Dep + 1\),然後查詢時查 \(K - Dep_L + 1\)
(當然也可以 特判加上當前根的貢獻)
這樣的話就要注意 \(K - Dep_L + 1\) 可能有 負數,在 查詢時要判掉
然後一個最佳化,我們可以更改 答案貢獻的順序
即 先減去兒子子樹的獨立貢獻,再加上 整棵樹的貢獻,這樣不用 \(DFS\) 兩遍
#include <bits/stdc++.h>
const int MAXN = 100055;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN];
int N, K, W, Cnt, Now, rt;
long long Ans;
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
struct Node {
int dis, dep;
inline bool operator < (const Node &a) const {
return dis < a.dis;
}
} P[MAXN];
inline void DFS2 (const int x, const int f, const int dis, const int dep) {
P[++ Cnt] = {dis, dep};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dis + E[i].w, dep + 1);
}
namespace BIT {
int T[MAXN];
#define lowbit(x) (x & -x)
inline void Add (int x) {
while (x <= N + 2) ++ T[x], x += lowbit (x);
}
inline void Del (int x) {
while (x <= N + 2) -- T[x], x += lowbit (x);
}
inline int Sum (int x) {
if (x < 0 || x > N + 2) return 0;
int Ret = 0;
while (x) Ret += T[x], x -= lowbit (x);
return Ret;
}
}
using namespace BIT;
inline void DFS3 (const int x, const int f) {
Vis[x] = 1, Cnt = 0;
int L, R, C = 1;
for (int i = H[x]; i; i = E[i].nxt) {
if (Vis[E[i].to] || E[i].to == f) continue ;
DFS2 (E[i].to, x, E[i].w, 1);
sort (P + C, P + Cnt + 1);
L = C, R = Cnt;
for (int i = C; i <= Cnt; ++ i) Add (P[i].dep + 1);
while (L <= R) {
while (R >= L && P[L].dis + P[R].dis > W) Del (P[R --].dep + 1);
if (R < L) break ;
Del (P[L].dep + 1), Ans -= Sum (K - P[L ++].dep + 1);
}
C = Cnt + 1;
}
P[++ Cnt] = {0, 0}, L = 1, R = Cnt;
sort (P + 1, P + Cnt + 1);
for (int i = 1; i <= Cnt; ++ i) Add (P[i].dep + 1);
while (L <= R) {
while (R >= L && P[L].dis + P[R].dis > W) Del (P[R --].dep + 1);
if (R < L) break ;
Del (P[L].dep + 1), Ans += Sum (K + 1 - P[L ++].dep);
}
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int v, w;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> K >> W;
for (int i = 2; i <= N; ++ i) cin >> v >> w, Add_Edge (v, i, w);
Now = N, DFS1 (1, 0), DFS3 (rt, 0);
cout << Ans << '\n';
return 0;
}
Luogu P5563 [Celeste-B] No More Running
每次 處理出每個兒子的子樹內每個點到當前根的距離,放到一個 std::multiset
裡
然後遍歷這些點,每次把 當前點所在子樹的所有點 從 std::multiset
裡面 刪掉
之後 multiset.lower_bound
查一下 MOD - Dis[i] - 1
可以得到 經過當前根,一端點為 \(i\) 的 最長路徑,更新答案即可
這裡應當 同時更新這條路徑兩端的答案,然後注意 端點為當前根的情況
最後 遍歷完一個兒子的子樹 時 把這個子樹所有點 重新插回 std::multiset
然後就做完了,竟然只要 \(95 ~ Lines\)(無空行 \(68 ~ Lines\))
#include <bits/stdc++.h>
const int MAXN = 100005;
int MOD = 1;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN], Ans[MAXN];
int N, K, Cnt, Now, rt;
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
struct Node {
int dis, x, nod;
inline bool operator < (const Node &a) const {
return dis == a.dis ? nod < a.nod : dis > a.dis;
}
} P[MAXN];
inline void DFS2 (const int x, const int f, const int nod, const int dis) {
P[++ Cnt] = {dis % MOD, x, nod};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, nod, dis + E[i].w);
}
multiset <Node> S;
inline void DFS3 (const int x, const int f) {
Vis[x] = 1, Cnt = 0, P[++ Cnt] = {0, x, 0};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, E[i].to, E[i].w);
for (int i = 1; i <= Cnt; ++ i) S.insert (P[i]);
int RPos = 2, LPos = 2;
for (int i = H[x]; i; i = E[i].nxt) {
if (Vis[E[i].to] || E[i].to == f) continue ;
while (P[RPos].nod == E[i].to && RPos <= Cnt) S.erase (P[RPos]), ++ RPos;
for (int k = LPos; k < RPos; ++ k) {
auto it = S.lower_bound (Node {MOD - P[k].dis - 1, 0, 0});
Ans[P[k].x] = max (Ans[P[k].x], P[k].dis + (*it).dis);
Ans[(*it).x] = max (Ans[(*it).x], P[k].dis + (*it).dis);
Ans[x] = max (Ans[x], P[k].dis % MOD);
}
while (P[LPos].nod == E[i].to && LPos <= Cnt) S.insert (P[LPos]), ++ LPos;
}
S.clear ();
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int u, v, w;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> MOD;
for (int i = 2; i <= N; ++ i)
cin >> u >> v >> w, Add_Edge (u, v, w);
Now = N, DFS1 (1, 0), DFS3 (rt, 0);
for (int i = 1; i <= N; ++ i) cout << Ans[i] << '\n';
return 0;
}
Luogu P2664 樹上游戲
有點神秘的這個題
相當於說 任選兩點,路徑上 顏色數 的 和
顯然任選的話一共 \(N ^ 2\) 種 情況,不需要再什麼 \(\div 2\) 之類的
我們把 貢獻分成兩部分來算,設當前分治中心(當前根)為 \(x\)
\(Part~1.\) 端點為 \(x\) 的 路徑統計,即 計算 \(x\) 的答案
我們以 \(Val_x\) 代表 \(x\) 的 顏色,\(Siz_x\) 代表 \(x\) 的 子樹大小,\(Col_x\) 代表 這個顏色是否出現
顯然,\(Val_x\) 會給 \(x\) 帶來 \(Siz_x\) 的 貢獻,並標記 \(Col_{val_x}\),表示 \(Val_x\) 這個顏色出現過
那麼 \(x\) 的 子樹內 有和 \(x\) 顏色相同 的怎麼辦?
現在 不考慮,到時候算貢獻的時候 會把它的貢獻去除
然後我們 遍歷 \(x\) 每個兒子的子樹
對於每個顏色(除了 \(Val_x\)),當它在 某個兒子子樹 內 第一次出現 時(設出現在 \(u\))
顯然它會給 \(x\) 帶來 \(Siz_u\) 的貢獻
一端為 \(x\),另一端只要在 \(u\) 子樹內,那麼 \(Val_u\) 就會 給這條路徑貢獻 \(1\) 的顏色數
於是我們對 每個兒子的子樹 \(DFS\),當 第一次出現 \(Val_u\) 時,標記 \(Col_{Val_u}\)
同時 \(x\) 的答案 加上 \(Siz_u\),當 退出這個點 時,把標記刪除
顯然 不同兒子子樹 不相互影響,也就是 每個子樹標記獨立
為方便後續,我們給 每個兒子子樹內某顏色第一次出現的位置 \(u\),都打上一個標記 \(Tmp_u\)
也就是 所有當時標記了 \(Col_{Val_u}\) 的點都打上 \(Tmp_u\),不刪除
這時候 \(x\) 的答案就已經 統計完了
\(Part ~ 2.\) 經過 \(x\) 的 路徑統計
我們 同樣列舉兒子子樹,顯然路徑兩端 不能在同一個兒子子樹
記錄 當前貢獻 \(sum\),就是 \(Part ~ 1.\) 中 \(x\) 的答案
還需要記錄 每種顏色的當前貢獻,記作 \(Cac_x\),存在 \(\sum Cac_x = sum\)
於是 進入子樹時,先把 \(sum\) 減去一個 \(Siz_{son}\),退出時 加回來
同時 對於子樹內所有有 \(Tmp_u\) 標記的 \(u\),減去 \(Siz_u\),顯然,子樹內不能互相貢獻
由於每個 \(Tmp_u\) 代表 特定顏色 貢獻,這裡減的時候 對應 \(Cac_{Val_u}\) 也要減
接著 \(DFS\) 這個子樹,對於一般的點,給它的答案加上 \(sum\) 即可
對於有 \(Tmp_u\) 標記的 \(u\),注意到,當 一個端點在 \(u\) 子樹內
另一個端點在 \(x\) 子樹外且不在任意與 \(u\) 同色點的子樹內 時,\(u\) 這個顏色會 有 \(1\) 的貢獻
而這樣的 另一個端點 個數即為 \(Siz_{rt} - Siz_x - Cac_{Val_u}\),於是 \(sum\) 需要加上 這個貢獻
由於 這個貢獻 顯然只對 \(u\) 所在子樹生效,故 進入 \(u\) 時 加上貢獻,退出時 注意撤銷
於是 遍歷完每個 兒子子樹,最後 正常點分治遞迴處理 即可
時間複雜度 \(O(N \log N)\),有 \(O(N)\) 的 神秘做法,但是 太高明瞭,加上不是 點分治,就沒寫
程式碼細節還是有點的,理清楚了再寫
#include <bits/stdc++.h>
const int MAXN = 100005;
using namespace std;
struct Edge {
int to, nxt;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v) {
E[++ tot] = {v, H[u]}, H[u] = tot;
E[++ tot] = {u, H[v]}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN], Val[MAXN];
int Cac[MAXN];
int N, K, Cnt, Now, sum, rt;
long long Sum[MAXN];
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
int Tmp[MAXN], Col[MAXN];
vector <int> S, G;
inline void add (const int x, const int top) {
S.push_back (x), Col[Val[x]] = Tmp[x] = top, sum += Siz[x], Sum[top] += Siz[x], Cac[Val[x]] += Siz[x];
}
inline void Del (const int x, const int f, const int top) {
if (Tmp[x] == top) G.push_back (x), Cac[Val[x]] -= Siz[x], sum -= Siz[x];
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Del (E[i].to, x, top);
}
inline void Add (const int x, const int f, const int val, const int top) {
int rval = val - Cac[Val[x]];
if (Tmp[x] == top) Cac[Val[x]] += rval, sum += rval;
Sum[x] += sum;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Add (E[i].to, x, val, top);
if (Tmp[x] == top) Cac[Val[x]] -= rval, sum -= rval;
}
inline void DFS2 (const int x, const int f, const int top) {
bool tmp = 0;
if (Col[Val[x]] != top) tmp = 1, add (x, top);
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, top);
if (tmp) Col[Val[x]] = 0;
}
inline void DFS3 (const int x, const int f) {
Vis[x] = 1, add (x, x);
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, x);
for (int i = H[x]; i; i = E[i].nxt) {
if (Vis[E[i].to] || E[i].to == f) continue ;
Cac[Val[x]] -= Siz[E[i].to], sum -= Siz[E[i].to];
Del (E[i].to, x, x), Add (E[i].to, x, Siz[x] - Siz[E[i].to], x);
Cac[Val[x]] += Siz[E[i].to], sum += Siz[E[i].to];
while (!G.empty ()) Cac[Val[G.back ()]] += Siz[G.back ()], sum += Siz[G.back ()], G.pop_back ();
}
while (!S.empty ()) Cac[Val[S.back ()]] = 0, S.pop_back ();
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], sum = rt = 0, DFS1 (E[i].to, x), DFS1 (rt, x), DFS3 (rt, x);
}
int u, v;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N;
for (int i = 1; i <= N; ++ i) cin >> Val[i];
for (int i = 2; i <= N; ++ i) cin >> u >> v, Add_Edge (u, v);
Now = N, DFS1 (1, 0), DFS1 (rt, 0), DFS3 (rt, 0);
for (int i = 1; i <= N; ++ i) cout << Sum[i] << '\n';
return 0;
}
CF150E Freezing with Style
給定 帶權樹,求邊數在 \([L, R]\) 間的路徑,使得 路徑邊權中位數最大,輸出 任一可行路徑端點
看到中位數,不好搞,考慮 二分答案 \(Ans\)
把所有 原始邊權 大於等於當前 \(Ans\) 的邊 邊權設為 \(1\),其餘 設為 \(-1\)
於是 判定 就是 樹上是否存在一條 合法路徑 邊權和大於 \(0\),這個 \(trick\) 好像很 典
於是我們其實就是求 樹上 邊數在 \([L, R]\) 間的路徑 邊權和最大值,和上一道題很像
但實際上還是比 上一個題 要強一些,主要在於 邊數限制是一個區間
上一道題限制 邊權等於 \(k\),相當於 邊數限制是一個常數
我們先跑出 每個點到當前根的邊數 同時 跑出邊權和
同樣的,對於 同一個邊數,我們只記錄 最大邊權和
顯然,對於一條 邊數 \(i\) 的路徑,我們需要 邊數在 \([L - i, R - i]\) 的路徑 與之匹配
容易發現,我們只需要這些路徑裡的 最大值,這就是一個 滑動視窗,於是 單調佇列維護 即可
這裡有一個細節,如果 直接順序遍歷兒子,那麼 單調佇列值域可能上到 \(Size_{max}\),
也就是 單次滑動視窗時間為 \(O(Size_{max})\),即 最大兒子子樹大小,複雜度比較假
考慮把兒子 按 子樹最大深度 從小到大 排序,先處理 深度較小的
這樣可以保證處理到 深度為 \(Dep\) 的子樹時,單調佇列只維護 深度小於等於 \(Dep\) 的部分
顯然 前面的子樹深度均不大於 \(Dep\),那麼維護大於 \(dep\) 的部分是 無意義的
這樣一次 滑動視窗的時間就是 \(O(Dep_{max})\),不大於 \(O(Size_{now})\),複雜度正確
顯然,一棵樹上一共 \(N\) 個點,每個點只會被 當成兒子一次
故 兒子按最大深度 排序這點 不是瓶頸
最後注意 維護邊權和同時維護端點,輸出的可 不是最大中位數
細節還挺多的,比如 不要在 \(DFS\) 裡面 建立
std::deque
,直接手寫會好一點可以看程式碼,這個題是 有高階拍子的
#include <bits/stdc++.h>
const int MAXN = 100005;
const int INF = 1e9;
using namespace std;
struct Edge {
int to, nxt, w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN], Dep[MAXN], Mxd[MAXN], Val[MAXN];
int N, K, Ans, Now, rt, L, R;
int Pos1, Pos2;
inline bool Cmp (const int a, const int b) {
return Mxd[a] < Mxd[b];
}
inline void DFS1 (const int x, const int f, const int d = 0) {
Siz[x] = 1, Max[x] = 0, Dep[x] = d, Mxd[x] = d;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x, d + 1), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]), Mxd[x] = max (Mxd[x], Mxd[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
struct DisPos {
int dis, pos;
inline bool operator < (const DisPos &a) const {
return dis < a.dis;
}
} Tmp[MAXN], Dis[MAXN];
inline void DFS2 (const int x, const int f, const int dep, const int dis) {
if (dis > Tmp[dep].dis) Tmp[dep] = {dis, x};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dep + 1, dis + E[i].w);
}
int Q[MAXN];
inline bool DFS3 (const int x, const int f) {
vector <int> P;
Vis[x] = 1;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
P.push_back (E[i].to), Val[E[i].to] = E[i].w;
sort (P.begin (), P.end (), Cmp);
int now, mxd = 0, l, r;
for (int i = 1; i <= Mxd[x]; ++ i) Dis[i].dis = - INF;
Dis[0] = {0, x};
for (auto i : P) {
DFS2 (i, x, Dep[i], Val[i]);
now = max (0, L - min (R, Mxd[i])), l = 1, r = 0;
for (int k = min (R, Mxd[i]); k >= 1; -- k) {
while (now <= mxd && now <= R - k) {
while (r >= l && Dis[now].dis > Dis[Q[r]].dis) -- r;
Q[++ r] = now, ++ now;
}
while (r >= l && Q[l] < L - k) ++ l;
if (r >= l && Q[l] <= R - k && Dis[Q[l]].dis + Tmp[k].dis >= 0)
return Pos1 = Dis[Q[l]].pos, Pos2 = Tmp[k].pos, 1;
}
for (int k = Dep[x]; k <= Mxd[i]; ++ k) Dis[k] = max (Dis[k], Tmp[k]);
for (int k = Dep[x]; k <= Mxd[i]; ++ k) Tmp[k] = {- INF, 0};
mxd = max (mxd, Mxd[i]);
}
for (auto i : P) {
Now = Siz[i], rt = 0, DFS1 (i, x), DFS1 (rt, x);
if (DFS3 (rt, x)) return 1;
}
return 0;
}
struct edge {
int u, v, w;
} G[MAXN];
inline bool Check (const int x) {
memset (H, 0, sizeof H), tot = 0;
memset (Vis, 0, sizeof Vis);
for (int i = 0; i <= N; ++ i) Tmp[i].dis = - INF;
for (int i = 2; i <= N; ++ i) Add_Edge (G[i].u, G[i].v, G[i].w >= x ? +1 : -1);
Now = N, rt = 0, DFS1 (1, 0), DFS1 (rt, 0);
return DFS3 (rt, 0);
}
inline void Solve (int l, int r) {
int m = (l + r) >> 1;
while (l <= r) {
m = (l + r) >> 1;
if (Check (m)) Ans = m, l = m + 1;
else r = m - 1;
}
Check (Ans);
}
int MinW, MaxW;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> L >> R;
for (int i = 2; i <= N; ++ i) {
cin >> G[i].u >> G[i].v >> G[i].w;
MinW = min (MinW, G[i].w), MaxW = max (MaxW, G[i].w);
}
Solve (MinW, MaxW);
cout << Pos1 << ' ' << Pos2 << '\n';
return 0;
}
Luogu P4292 [WC2010] 重建計劃
上個題的 雙倍經驗,就是把 中位數 改成 平均值,但是 實數二分
直接改程式碼,把權值從 \(\pm 1\) 改成 \(Ans - w_i\),正確性就有保證,精度沒有被卡
難受的是 被卡常了,記憶重心 才能過
注意到 記錄重心 這個技巧,還 不能直接對每個點記錄
因為點分治時並 不是從當前根到當前根兒子 而是到當前根兒子子樹重心
也就是 當前根兒子 可能會被 多次訪問,其代表的子樹是不一樣的
將重心記錄到 當前根意義下每個兒子 裡 會寄掉
應當把 當前根兒子子樹的重心集 記錄到 當前根 上,這樣就正確了
#include <bits/stdc++.h>
const int MAXN = 100005;
const double INF = 1e+8;
const double EPS = 1e-4;
using namespace std;
struct Edge {
int to, nxt;
double w;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const double w) {
E[++ tot] = {v, H[u], w}, H[u] = tot;
E[++ tot] = {u, H[v], w}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN], Dep[MAXN], Mxd[MAXN];
double Val[MAXN], Ans;
int N, K, Now, rt, L, R;
int Pos1, Pos2;
inline bool Cmp (const int a, const int b) {
return Mxd[a] < Mxd[b];
}
inline void DFS1 (const int x, const int f, const int d = 0) {
Siz[x] = 1, Max[x] = 0, Dep[x] = d, Mxd[x] = d;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x, d + 1), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]), Mxd[x] = max (Mxd[x], Mxd[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
struct DisPos {
double dis;
int pos;
inline bool operator < (const DisPos &a) const {
return dis < a.dis;
}
} Tmp[MAXN], Dis[MAXN];
inline void DFS2 (const int x, const int f, const int dep, const double dis) {
if (dis > Tmp[dep].dis) Tmp[dep] = {dis, x};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dep + 1, dis + E[i].w);
}
int Q[MAXN];
vector <int> S[MAXN];
inline bool DFS3 (const int x, const int f) {
vector <int> P;
Vis[x] = 1;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
P.push_back (E[i].to), Val[E[i].to] = E[i].w;
sort (P.begin (), P.end (), Cmp);
int now, mxd = 0, l, r, pos = 0;
for (int i = 1; i <= Mxd[x]; ++ i) Dis[i].dis = - INF;
Dis[0] = {0, x};
for (auto i : P) {
DFS2 (i, x, Dep[i], Val[i]);
now = max (0, L - min (R, Mxd[i])), l = 1, r = 0;
for (int k = min (R, Mxd[i]); k >= 1; -- k) {
while (now <= mxd && now <= R - k) {
while (r >= l && Dis[now].dis > Dis[Q[r]].dis) -- r;
Q[++ r] = now, ++ now;
}
while (r >= l && Q[l] < L - k) ++ l;
if (r >= l && Q[l] <= R - k && Dis[Q[l]].dis + Tmp[k].dis >= 0)
return Pos1 = Dis[Q[l]].pos, Pos2 = Tmp[k].pos, 1;
}
for (int k = Dep[x]; k <= Mxd[i]; ++ k) Dis[k] = max (Dis[k], Tmp[k]);
for (int k = Dep[x]; k <= Mxd[i]; ++ k) Tmp[k] = {- INF, 0};
mxd = max (mxd, Mxd[i]);
}
bool k = S[x].empty ();
for (auto i : P) {
Now = Siz[i], rt = 0;
if (k) DFS1 (i, x), S[x].push_back (rt);
else rt = S[x][pos ++];
DFS1 (rt, x);
if (DFS3 (rt, x)) return 1;
}
return 0;
}
struct edge {
int u, v;
double w;
} G[MAXN];
inline bool Check (const double x) {
memset (H, 0, sizeof H), tot = 0;
memset (Vis, 0, sizeof Vis);
for (int i = 0; i <= N; ++ i) Tmp[i].dis = - INF;
for (int i = 2; i <= N; ++ i) Add_Edge (G[i].u, G[i].v, G[i].w - x);
Now = N, rt = 0, DFS1 (1, 0), DFS1 (rt, 0);
return DFS3 (rt, 0);
}
inline void Solve (double l, double r) {
double m = (l + r) / 2.0;
while (l + EPS < r) {
m = (l + r) / 2.0;
if (Check (m)) Ans = m, l = m;
else r = m;
}
Check (Ans);
}
double MinW, MaxW;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> L >> R;
for (int i = 2; i <= N; ++ i) {
cin >> G[i].u >> G[i].v >> G[i].w;
MinW = min (MinW, G[i].w), MaxW = max (MaxW, G[i].w);
}
Solve (MinW, MaxW);
cout << fixed << setprecision (3) << Ans << '\n';
// cout << Pos1 << ' ' << Pos2 << '\n';
return 0;
}
UVA12161 鐵人比賽 Ironman Race in Treeland
給定一棵 帶權樹,每一條邊包含 長度 \(Dep\) 和 費用 \(Dis\) 兩個引數
選擇一條 總費用不超過給定值 的路徑,使 路徑總長度最大
感覺沒法簡單維護,直接上 動態開點線段樹
\(DFS\) 兒子子樹得到 每個點關於當前根 的 \(Dep, Dis\)
在詢問之後 每次往對應 \(Dis\) 位置插入 \(Dep\)
詢問即是查詢 \([0, K - Dis]\) 區間內 \(Dep\) 的 最大值,每個點更新就行
注意 多組資料
線段樹的清空 是一個需要注意的細節,可以參考以下實現
#include <bits/stdc++.h>
const int MAXN = 100005;
const int LOGN = 40;
const int INF = 1e9;
using namespace std;
namespace SegTree {
struct TNode {
int L, R;
int lc, rc;
int Max;
} T[MAXN * LOGN];
#define LC T[x].lc
#define RC T[x].rc
#define M ((T[x].L + T[x].R) >> 1)
int Tot = 0, Rt = 0;
inline void Maintain (const int x) {
T[x].Max = max (T[LC].Max, T[RC].Max);
}
inline void Ins (const int p, const int v, int &x, const int L = 0, const int R = INF) {
if (!x) x = ++ Tot, T[x].Max = v, LC = RC = 0;
T[x].L = L, T[x].R = R;
if (L == R) return T[x].Max = max(T[x].Max, v), void ();
if (p <= M) Ins (p, v, LC, L, M);
else Ins (p, v, RC, M + 1, R);
Maintain (x);
}
inline void Del (const int p, const int x = 1) {
if (!x || T[x].L == T[x].R) return ;
int lc = LC, rc = RC, m = M;
LC = RC = T[x].L = T[x].R = T[x].Max = 0;
if (p <= m) Del (p, lc);
else Del (p, rc);
}
inline int Query (const int L, const int R, const int x = 1) {
if (!x || L > T[x].R || T[x].L > R) return 0;
if (L <= T[x].L && T[x].R <= R) return T[x].Max;
return max (Query (L, R, LC), Query (L, R, RC));
}
#undef M
}
struct Edge {
int to, nxt, w, d;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v, const int w, const int d) {
E[++ tot] = {v, H[u], w, d}, H[u] = tot;
E[++ tot] = {u, H[v], w, d}, H[v] = tot;
}
bool Vis[MAXN];
int Siz[MAXN], Max[MAXN];
int N, K, W, Cnt, Now, rt;
inline void DFS1 (const int x, const int f) {
Siz[x] = 1, Max[x] = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS1 (E[i].to, x), Siz[x] += Siz[E[i].to], Max[x] = max (Max[x], Siz[E[i].to]);
Max[x] = max (Max[x], Now - Siz[x]);
if (Max[x] < Max[rt] || !rt) rt = x;
}
struct Node {
int dep, dis;
} P[MAXN];
int MaxDep;
inline void DFS2 (const int x, const int f, const int dep, const int dis) {
P[++ Cnt] = {dep, dis};
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
DFS2 (E[i].to, x, dep + E[i].d, dis + E[i].w);
}
inline void DFS3 (const int x, const int f) {
Vis[x] = 1, SegTree::Ins (0, 0, SegTree::Rt);
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f) {
Cnt = 0, DFS2 (E[i].to, x, E[i].d, E[i].w);
for (int k = 1; k <= Cnt; ++ k)
if (K >= P[k].dis) MaxDep = max (MaxDep, SegTree::Query (0, K - P[k].dis) + P[k].dep);
for (int k = 1; k <= Cnt; ++ k) SegTree::Ins (P[k].dis, P[k].dep, SegTree::Rt);
}
SegTree::Tot = SegTree::Rt = 0;
for (int i = H[x]; i; i = E[i].nxt)
if (!Vis[E[i].to] && E[i].to != f)
Now = Siz[E[i].to], rt = 0, DFS1 (E[i].to, x), DFS3 (rt, x);
}
int u, v, w, d;
inline void Solve (const int Sol) {
cin >> N >> K;
for (int i = 2; i <= N; ++ i)
cin >> u >> v >> w >> d, Add_Edge (u, v, w, d);
Now = N, rt = 0, DFS1 (1, 0), DFS3 (rt, 0);
cout << "Case " << Sol << ": " << MaxDep << '\n';
memset (H, 0, sizeof H);
tot = 0, SegTree::Tot = SegTree::Rt = 0, MaxDep = 0;
memset (Vis, 0, sizeof Vis);
}
int Ti;
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> Ti;
for (int i = 1; i <= Ti; ++ i) Solve (i);
return 0;
}
Luogu P3292 [SCOI2016] 幸運數字
和 點分治 沒啥關係啊,雖然好像有 點分治做法
看到 子集異或最大值,可以想到 異或線性基
注意到 線性基是可以合併的,複雜度 \(O (\log ^ 2 V)\),於是很快想到一個 暴力做法
即 預處理 倍增 \(LCA\) 的 \(2 ^ k\) 級祖先的同時 處理到 \(2 ^ k\) 級祖先這段路 的 異或線性基
最後和 \(LCA\) 一起 倍增合併 即可,時間複雜度 \(O((N + Q) \log N \log ^ 2 V)\)
有 \(1.4e10\) 左右,即使有 \(6s\) 也只能過 \(90 ~ pts\),考慮最佳化
如果 不想換掉倍增 的話怎麼辦呢?
注意到 \(Q\) 比 \(N\) 整整大了 \(10\) 倍,如果能最佳化 詢問複雜度,這個問題可能就解決了
容易發現,線性基是一種可以重複貢獻的資訊,也就是可以 支援 \(RMQ\) 來做
於是 查詢複雜度就變成了 \(O(Q \log ^ 2 V)\),總複雜度 \(O(N \log N \log ^ 2 V + Q \log ^ 2 V)\)
有 \(2e9\),但是 常數較小,可以透過
#include <bits/stdc++.h>
const int MAXN = 20005;
const int LOGN = 60;
const int logn = 15;
using namespace std;
struct Edge {
int to, nxt;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v) {
E[++ tot] = {v, H[u]}, H[u] = tot;
E[++ tot] = {u, H[v]}, H[v] = tot;
}
struct Basis {
unsigned long long A[LOGN + 5] = {0};
inline void Clr () {
for (int i = 0; i <= LOGN; ++ i) A[i] = 0;
}
inline void Ins (unsigned long long x) {
for (int i = LOGN; ~ i; -- i)
if ((x >> i) & 1) A[i] ? x ^= A[i] : (A[i] = x, i = 0);
}
inline unsigned long long Max () {
unsigned long long Ret = 0;
for (int i = LOGN; ~ i; -- i) Ret = max (Ret, Ret ^ A[i]);
return Ret;
}
} G[MAXN][logn + 1];
inline void Merge (Basis &a, const Basis &b) {
for (int i = LOGN; ~ i; -- i) a.Ins (b.A[i]);
}
int N, K;
int u, v;
long long V[MAXN];
int D[MAXN], LOG[MAXN];
int F[MAXN][logn + 1];
inline void DFS (const int x, const int f) {
F[x][0] = f, G[x][0].Ins (V[x]), D[x] = D[f] + 1;
for (int i = 1; i <= logn; ++ i) {
F[x][i] = F[F[x][i - 1]][i - 1];
G[x][i] = G[x][i - 1];
Merge (G[x][i], G[F[x][i - 1]][i - 1]);
}
for (int i = H[x]; i; i = E[i].nxt)
if (E[i].to != f) DFS (E[i].to, x);
}
inline int LCA (int u, int v) {
if (D[u] < D[v]) swap (u, v);
while (D[u] > D[v]) u = F[u][LOG[D[u] - D[v]]];
if (u == v) return u;
for (int i = logn; ~ i; -- i)
if (F[u][i] != F[v][i])
u = F[u][i], v = F[v][i];
return F[u][0];
}
inline int Kth (int u, int k) {
if (k < 0) return u;
while (k) u = F[u][LOG[k]], k -= (1 << LOG[k]);
return u;
}
inline unsigned long long Que (int u, int v) {
if (D[u] < D[v]) swap (u, v);
int lca = LCA (u, v), lu = LOG[D[u] - D[lca]], lv = LOG[D[v] - D[lca]];
int su = Kth (u, D[u] - D[lca] - (1 << lu) + 1), sv = Kth (v, D[v] - D[lca] - (1 << lv) + 1);
Basis x;
Merge (x, G[u][lu]);
Merge (x, G[su][lu]);
Merge (x, G[v][lv]);
Merge (x, G[sv][lv]);
return x.Max ();
}
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> K;
for (int i = 1; i <= N; ++ i) cin >> V[i];
for (int i = 2; i <= N; ++ i) cin >> u >> v, Add_Edge (u, v);
DFS (1, 0);
for (int i = 2; i <= N; ++ i) LOG[i] = LOG[i >> 1] + 1;
for (int i = 1; i <= K; ++ i) {
cin >> u >> v;
cout << Que (u, v) << '\n';
}
return 0;
}
然後這個東西 跑得比 \(\textsf H \color {red} \textsf {anghang}\) 還慢,火大!
注意到我們的這個路徑 實質上就是一種區間資訊,區間子集異或最大值
可以使用 字首線性基 進行最佳化,字首線性基 相關這裡略過
實現上,我們的線性基 從父親處繼承,每次只插入當前節點的權值
用 節點深度 表示 位置
於是我們 只需要處理 \(LCA\),查詢時 \(u \to LCA\) 和 \(LCA \to v\) 兩段 分別區間查詢 即可
時間複雜度 \(O((Q + N) (\log N + \log V))\),實際上只需要 \(\frac {1} {10}\) 的時間 就能跑完
#include <bits/stdc++.h>
const int MAXN = 20005;
const int LOGN = 60;
const int logn = 15;
using namespace std;
struct Edge {
int to, nxt;
} E[MAXN << 1];
int H[MAXN], tot;
inline void Add_Edge (const int u, const int v) {
E[++ tot] = {v, H[u]}, H[u] = tot;
E[++ tot] = {u, H[v]}, H[v] = tot;
}
struct Basis {
unsigned long long A[LOGN + 5] = {0};
unsigned int P[LOGN + 5] = {0};
inline void Clr () {
for (int i = 0; i <= LOGN; ++ i) A[i] = 0;
for (int i = 0; i <= LOGN; ++ i) P[i] = 0;
}
inline void Ins (unsigned long long x, unsigned int p) {
for (int i = LOGN; ~ i; -- i) {
if ((x >> i) & 1) {
if (A[i]) {
if (P[i] < p) swap (P[i], p), swap (A[i], x);
x ^= A[i];
} else A[i] = x, P[i] = p, i = 0;
}
}
}
inline unsigned long long Max (const unsigned int L) {
unsigned long long Ret = 0;
for (int i = LOGN; ~ i; -- i) {
if (P[i] < L) continue ;
Ret = max (Ret, Ret ^ A[i]);
}
return Ret;
}
} G[MAXN];
inline void Merge (Basis &a, const Basis &b, const unsigned int d) {
for (int i = LOGN; ~ i; -- i) if (b.A[i] && b.P[i] >= d) a.Ins (b.A[i], b.P[i]);
}
int N, K;
int u, v;
long long V[MAXN];
int D[MAXN], LOG[MAXN];
int F[MAXN][logn + 1];
inline void DFS (const int x, const int f) {
F[x][0] = f, D[x] = D[f] + 1, G[x] = G[f], G[x].Ins (V[x], D[x]);
for (int i = 1; i <= logn; ++ i) F[x][i] = F[F[x][i - 1]][i - 1];
for (int i = H[x]; i; i = E[i].nxt)
if (E[i].to != f) DFS (E[i].to, x);
}
inline int LCA (int u, int v) {
if (D[u] < D[v]) swap (u, v);
while (D[u] > D[v]) u = F[u][LOG[D[u] - D[v]]];
if (u == v) return u;
for (int i = logn; ~ i; -- i)
if (F[u][i] != F[v][i])
u = F[u][i], v = F[v][i];
return F[u][0];
}
inline unsigned long long Que (int u, int v) {
int lca = LCA (u, v);
Basis x; x.Clr ();
Merge (x, G[u], D[lca]);
Merge (x, G[v], D[lca]);
return x.Max (D[lca]);
}
int main () {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> N >> K;
for (int i = 1; i <= N; ++ i) cin >> V[i];
for (int i = 2; i <= N; ++ i) cin >> u >> v, Add_Edge (u, v);
DFS (1, 0);
for (int i = 2; i <= N; ++ i) LOG[i] = LOG[i >> 1] + 1;
for (int i = 1; i <= K; ++ i) {
cin >> u >> v;
cout << Que (u, v) << '\n';
}
return 0;
}