我們發現要解決乙個樹上的連通塊問題,解決這種問題的時候我們不妨先隨便選乙個根,如果要選某兩個點則他們到n的路徑上的點都會被選就變成了乙個樹形揹包問題。
注意這裡是多重揹包,所以我們可以用單調佇列優化,時間複雜度$o(n^2m)$。
考慮暴力選根的時候會把很多重複的情況算進去,所以我們可以用點分治,只計算根的孩子之間的貢獻,遞迴子樹時其餘兄弟節點就不用管了。
因為每次選的是重心,所以子樹大小必然減一半,時間複雜度$o(nm\log)$。
點分治+樹形揹包,這是一種常見的處理樹上連通塊的方法。
因為加了單調佇列優化,所以要注意樹形揹包時倒著做(即從葉節點開始)。
#include #include#include
#include
using
namespace
std;
const
int n = 5010, m = 40010, inf = 0x3f3f3f3f
;struct
nodeedge[n
<< 1
];int
head[n], tot;
intt;
intn, m;
intsz[n], mx[n], rt;
intw[n], v[n], d[n];
intdfn[n], bl[n], dep;
intdp[n][m];
intans;
bool
vis[n];
void get_root(int x, int tot_size, int
fa)
}mx[x] = max(mx[x], tot_size -sz[x]);
if (mx[x]
}void dfs(int x, int
fa)
}void cmax(int &x, int
y) int
q[n];
void solve(int
x) }
for (int i = dep; i >= 1; i--)
int a =v[dfn[i]];
int b =w[dfn[i]];
int c =d[dfn[i]];
for (int j = 0; j < a; j++) }}
for (int i = 1; i <= m; i++)
for (int i = head[x]; i; i =edge[i].pre)
}int
read()
while
(isdigit(ch))
return ret *f;
}void write(int
x) void print(int
x) write(x);
putchar('\n
');}void
init()
void add(int u, int
vv) ;
head[u] =tot;
} int
main()
for (int i = 1; i <= n; i++)
for (int i = 1; i <= n; i++)
for (int i = 1, u, vv; i < n; i++)
rt = 0
; get_root(
1, n, 0
); solve(rt);
print(ans);
}return0;
}
樹形依賴揹包
問題大意 給出一棵樹,根節點為1,每個點有毒素和收穫。要求毒素不超過給定值的情況下使收穫最大。乙個點的父親節點被選取後這個點才能被選取。首先弄出dfs序,也記錄下每個點其子樹及自身的大小。每個點都能夠被選或不選,如果選了才會考慮它子樹。設f i j 表示dfs序上第i位上的點在其子樹及自身上選取了毒...
樹形揹包總結
目錄 2 有物品大小 3 物品大小為1,有k的限制。二 dfs序上dp 例題1例題2 例題3總結下 樹形揹包,就是說,在樹上選乙個包含根的連通塊,或揹包存在依賴關係 選父才能選子 或者需要知道每個點的子樹中選了多少 通常,我們有兩種方法 我們設 dp i,j 表示在i的子節點中選j個的狀態。在轉移時...
樹形揹包DP
include using namespace std const int n 310,m n 2 int h n ne m v m idx int w n int dp n n int n,m void add int a,int b void dfs int u for int j m j 0 ...