樹上揹包的上下界優化
最近做了幾道樹上揹包的題目,很多題目的資料範圍都很小,但實際上樹上揹包有多種方式可以優化到 \(O(nm)\) (\(n\) 為節點數,\(m\) 為體積的值域),比如先序遍歷優化(何森《先序遍歷用於優化樹形揹包問題》),求泛化物品的並(徐持衡《淺談幾類揹包題》)……經過一番學習,覺得還是上下界優化理解起來最簡單,也比較好寫,適用範圍廣,唯一比其它做法複雜的地方就是複雜度分析。
例題講解
這裡以一道經典的樹上揹包作為例題:【資料加強版】選課
直接把我出的資料加強版放上來了..反正題面裡有原題連結QAQ
注:本文中用 \(a_i\) 代指題面中的 \(s_i\) 。
\(O(nm^2)\) 做法
用 \(f_{u,i}\) 表示以 \(u\) 為根的子樹中選 \(i\) 門課的最大得分,那麼 \(f_{u,i}=\min\limits_{\forall fa[v_j]=u,\sum k_j=i-1}(\sum f[v_j][k_j])+a_u\),而這個轉移可以通過揹包實現,依次合併每棵子樹,每次合併時列舉 \(i\) 和 \(k_j\) ,\(f_{u,i}=\max(f_{u,i},f_{u,i-k_j}+f_{v_j,k_j})\) 。
需要倒序列舉 \(i\) 防止狀態在轉移前被覆蓋。否則的話dp陣列要多一維。
由於可能是森林,所有沒有直接先修課的節點,父親視為節點 \(0\)
參考程式碼:
void dfs(int u) { f[u][1]=a[u]; int i,j,k,v; for (i=head[u];i;i=nxt[i]) { v=to[i]; dfs(v); for (j=m+1;j>=1;--j) { for (k=1;k<j;++k) { f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]); } } } }
上下界優化
注意揹包轉移的這部分:
for (j=m+1;j>=1;--j)
{
for (k=1;k<j;++k)
{
f[u][j]=max(f[u][j],f[u][k]+f[v][j-k]);
}
}
實際上,這裡面有很多狀態都是沒有意義的:
轉移時已經合併了大小之和為 \(s\) 的一些子樹,那麼 \(f_{u,i}(i>s)\) 實際上是沒有意義的。
\(f_{v,i}(i>siz[v])\) 也是沒有意義的。
\(f_{u,i}(i>m)\) 是沒有作用的。
所以,可以對 \(j\) 和 \(k\) 的列舉範圍進行優化:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
void dfs(int u);
void add(int u,int v);
const int N=2510;
int head[N],nxt[N],to[N],cnt;
int n,m,a[N],f[N][N],siz[N];
int main()
{
int i,k;
scanf("%d%d",&n,&m);
for (i=1;i<=n;++i)
{
scanf("%d%d",&k,a+i);
add(k,i);
}
dfs(0);
printf("%d",f[0][m+1]);
return 0;
}
void add(int u,int v)
{
nxt[++cnt]=head[u];
head[u]=cnt;
to[cnt]=v;
}
void dfs(int u)
{
siz[u]=1;
f[u][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<j;++k)
{
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
}
}
siz[u]+=siz[v];
}
}
複雜度分析
可以參考這篇部落格。
形象的解釋
每個點對都只會在 \(lca\) 處合併一次,所以總的複雜度是 \(O(n^2)\) 的。
這個解釋很簡潔,需要自己意會一下..
嚴格?證明
令 \(T_u\) 為處理子樹 \(u\) 的總用時,那麼:
\(\begin{aligned}T_u&=\left(\sum\limits_{\forall fa[v_i]=u}T_{v_i}\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_{\forall fa[v_i]=u}siz[v_i]\times(siz[u]+1)\\&=siz[u]^2\end{aligned}\)
對於葉子節點 \(u\) ,\(T(u)=1\) ,是 \(O(siz[u]^2)\) 的。
對於兒子都是葉子節點的節點 \(u\),由於平方和小於和平方,\(\sum\limits_{\forall fa[v_i]=u}T_{v_i}\) 也是 \(O(siz[u]^2)\) 的。
可以這樣遞迴地說明,對於任意節點 \(u\) ,\(\sum\limits_{\forall fa[v_i]=u}T_{v_i}\) 都是 \(O(siz[u]^2)\) 的。
又因為 \(t(u)\) 是 \(O(siz[u]^2)\) 的,\(T(u)\) 就是 \(O(siz[u]^2)\) 的。
所以解決整個問題就是 \(O(n^2)\) 的。
嚴格!證明
列舉過程中還要對 \(m\) 取 min ,所以應該是這樣的:
\(\begin{aligned}t_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{aligned}\)
所以,\(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_{\forall fa[v_i]=u,siz[v_i]\le m}T_{v_i}\) 是 \(O\left(\left(\sum\limits_{\forall fa[v_i]=u,siz[v_i]\le m}siz[v_i]\right)^2\right)\) 的;\(\sum\limits_{\forall fa[v_i]=u,siz[v_i]>m}T_{v_i}\) 是 \(O\left(m\times\sum\limits_{\forall fa[v_i]=u,siz[v_i]>m}siz[v_i]\right)\) 的;所以,\(T(u)\) 是 \(O(m\times siz[u])\) 的。
所以,解決整個問題是 \(O(nm)\) 的。