這道題也有用樹上莫隊的解法,這裡再給乙個樹上啟發式合併的解法。
樹上啟發式合併,類似輕重鏈剖分,先算出每個節點的重兒子,然後計算答案時先遞迴計算輕兒子的答案,標記clr為true,然後計算重兒子的答案,clr為false,然後把輕兒子的節點暴力插到重兒子裡,最後把父節點自己加入。
int n;
vectorg[maxn];
int siz[maxn], mch[maxn];
void dfs1(int u, int p)
}
由於樹上啟發式合併並不關心深度,所以沒有必要維護深度。
void calc(int u, int p, int skip, int d)
}void dfs2(int u, int p, bool keep)
if (mch[u])
dfs2(mch[u], u, true);
calc(u, p, mch[u], 1);
for (pii &q : q[u])
if (!keep)
calc(u, p, 0, -1);
}
然後是主要的計算過程dfs2,dfs2優先進入所有的輕兒子,並且不keep輕兒子的答案,保持樹狀陣列為空。然後進入重兒子計算並keep重兒子的結果。這裡使用乙個輔助函式calc,calc的修改值為1時表示向樹狀陣列中新增,然後命令其在新增時skip掉重兒子。計算完畢後樹狀陣列中存著這棵子樹對應的狀態,然後取出所有的詢問進行回答。那之後,假如不keep樹狀陣列,呼叫calc修改值為-1,並且不跳過重兒子,把整棵子樹刪除乾淨。
時間複雜度為 \(o(nlog^2n)\)
這裡的查詢要去重,所以要先計算再查詢。而且要注意cache的命中。一次樹遍歷就統計出所有的資訊,把常用的區域性值放在陣列的低維。
int n, k;
int a[maxn];
vectorg[maxn];
int siz[maxn], mch[maxn];
void dfs1(int u, int p)
}int cnt[1 << 20][17][2];
ll ans;
void calc1(int u, int p, int lca)
for (int &v : g[u])
}void calc2(int u, int p)
for (int &v : g[u])
}void calc3(int u, int p)
}void dfs2(int u, int p, bool keep)
if (mch[u])
dfs2(mch[u], u, true);
int val = a[u];
for (int k = 16; k >= 0; --k)
for (int &v : g[u])
if (!keep)
}}void solve()
dfs1(1, 0);
dfs2(1, 0, true);
printf("%lld\n", ans);
}
樹上啟發式合併
解決樹上統計問題,o n log n o n log n o n lo g n 可以結合線段樹等資料結構維護深度上的資訊 部落格 入門題 const int maxn 1e5 7 const int mod 1e9 7 ll n,m,u,v,mx,sum vector int mp maxn int...
樹上啟發式合併
樹上啟發式合併,一種美妙的黑科技,可以用普通的優化讓你 n 2 變成嚴格 n log 解決一些類似 樹上數顏色,樹上查眾數 這樣的問題 首先你要知道暴力為什麼是 n 2 的 以這個圖為例 每次你從乙個節點開始向下搜,你從1節點搜到3,搜完這個子樹然後你需要把3存的col等資訊刪去再遍歷另乙個子樹才是...
樹上啟發式合併總結
某一天發現一道樹上啟發式合併裸題,但我不會寫 學習並刷了兩天的題,是時候來寫個總結了 樹上啟發式合併 dsu on tree 是乙個在o n logn o nlogn o nlog n 時間內解決許多樹上問題的有力演算法。但它的中心其實是 暴力!沒錯,它正是由暴力優化而來。我們先看一道例題 cf60...