Shopping(樹形揹包 點分治)

2022-05-11 15:49:37 字數 1790 閱讀 3075

我們發現要解決乙個樹上的連通塊問題,解決這種問題的時候我們不妨先隨便選乙個根,如果要選某兩個點則他們到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 ...