最近做了幾道樹上揹包的題目,很多題目的資料範圍都很小,但實際上樹上揹包有多種方式可以優化到 \(o(nm)\) (\(n\) 為節點數,\(m\) 為體積的值域),比如先序遍歷優化(何森《先序遍歷用於優化樹形揹包問題》),求泛化物品的並(徐持衡《**幾類揹包題》)……經過一番學習,覺得還是上下界優化理解起來最簡單,也比較好寫,適用範圍廣,唯一比其它做法複雜的地方就是複雜度分析。
這裡以一道經典的樹上揹包作為例題:【資料加強版】選課
直接把我出的資料加強版放上來了..反正題面裡有原題鏈結qaq
注:本文中用 \(a_i\) 代指題麵中的 \(s_i\) 。用 \(f_\) 表示以 \(u\) 為根的子樹中選 \(i\) 門課的最大得分,那麼 \(f_=\min\limits_(\sum f[v_j][k_j])+a_u\),而這個轉移可以通過揹包實現,依次合併每棵子樹,每次合併時列舉 \(i\) 和 \(k_j\) ,\(f_=\max(f_,f_+f_)\) 。
需要倒序列舉 \(i\) 防止狀態在轉移前被覆蓋。否則的話dp陣列要多一維。
由於可能是森林,所有沒有直接先修課的節點,父親視為節點 \(0\),實際上就要選 \(m+1\) 個節點。
void dfs(int u)
(i>s)\) 實際上是沒有意義的。
\(f_(i>siz[v])\) 也是沒有意義的。
\(f_(i>m)\) 是沒有作用的。
所以,可以對 \(j\) 和 \(k\) 的列舉範圍進行優化:
void dfs(int u)
t_u&=\left(\sum\limits_t_\right)+t_u\\\\t_u&=1+(1+siz[v_1])\times siz[v_1]+(1+siz[v_1]+siz[v_2])\times siz[v_2]+\cdots+siz[u]\times siz[v_k]\\&=1+\sum\limits_siz[v_i]\times(siz[u]+1)\\&=siz[u]^2\end\)
對於葉子節點 \(u\) ,\(t(u)=1\) ,是 \(o(siz[u]^2)\) 的。
對於兒子都是葉子節點的節點 \(u\),由於平方和小於和平方,\(\sum\limits_t_\) 也是 \(o(siz[u]^2)\) 的。
可以這樣遞迴地說明,對於任意節點 \(u\) ,\(\sum\limits_t_\) 都是 \(o(siz[u]^2)\) 的。
又因為 \(t(u)\) 是 \(o(siz[u]^2)\) 的,\(t(u)\) 就是 \(o(siz[u]^2)\) 的。
所以解決整個問題就是 \(o(n^2)\) 的。
列舉過程中還要對 \(m\) 取 min ,所以應該是這樣的:
\(\begint_u&=1+\min(m,1+siz[v_1])\times \min(m,siz[v_1])+\min(m,1+siz[v_1]+siz[v_2])\times \min(m,siz[v_2])+\cdots+\min(m,siz[u])\times \min(m,siz[v_k])\\&\le m\times siz[u]\end\)
所以,\(t(u)\) 是 \(o(\min(siz[u],m)\times siz[u])\) 的。
對於 \(siz[u]\le m\),\(t(u)\) 是 \(o(siz[u]^2)\) 的。
對於 \(siz[u]>m\),\(\sum\limits_t_\) 是 \(o\left(\left(\sum\limits_siz[v_i]\right)^2\right)\) 的;\(\sum\limits_t_\) 是 \(o\left(m\times\sum\limits_siz[v_i]\right)\) 的;所以,\(t(u)\) 是 \(o(m\times siz[u])\) 的。
所以,解決整個問題是 \(o(nm)\) 的。
【資料加強版】道路重建
我出的那兩道資料加強版略有些毒瘤..(\(n\times m\le 10^8\))
大約需要這樣寫:
#include #include #include using namespace std;
void dfs(int u);
void add(int u,int v);
const int n=100010;
int head[n],nxt[n],to[n],cnt;
int n,m,a[n],f[100000010],siz[n];
int main()
dfs(0);
printf("%d",f[m+1]);
return 0;
}void add(int u,int v)
void dfs(int u)
{ siz[u]=1;
f[u*(m+2)+1]=a[u];
int i,j,k,v;
for (i=head[u];i;i=nxt[i])
{v=to[i];
dfs(v);
for (j=min(m+1,siz[u]+siz[v]);j>=1;--j)
{for (k=max(1,j-siz[u]);k<=siz[v]&&k一開始我在洛谷發了篇選課的題解,然後沒過...
那篇題解用的是求泛化物品的並(徐持衡《**幾類揹包題》)
雖然說洛谷好像還沒有上下界優化的題解..但最近好幾篇題解沒過審,都不太想在洛谷發題解了...
XJOI tree 樹上揹包 奇怪優化
題解 考這場考試的前一天晚上逛大神部落格,看見別人有寫樹上揹包,嗯,一笑而過了。結果第二天xjoi的提高組模擬賽就考了。於是不會,然後今天在上課的時候發呆的時候,自己腦補出來樹上揹包的做法。嗯,看了一下大神的部落格,我發現大神的樹上揹包是用的記憶化搜尋,我最開始的想法是基於拓撲排序從下往上dp,嗯,...
樹上揹包練習
p2014 ctsc1997 選課 p2014 ctsc1997 選課 solution 樹上揹包模板題 因為有多節課是沒有先修課的,所以並不是只有一棵樹,用乙個0號點作為沒有先修課的課程的先修課,這樣就合併成了一棵樹,只要選取m 1個點 必選0 即可。轉移方程 dp u j max dp u j ...
重建道路 樹上揹包
初始化 void init struct edges edge maxm 1 無向圖則需要乘2 inline void add int u,int v head u cnt int dp m m siz m tmp m int n,m void dfs int u,int fa siz u siz ...