ACM 樹上揹包DP總結
阿新 • • 發佈:2020-08-17
題目一
CCF 201909-5 城市規劃(http://118.190.20.162/view.page?gpid=T90)
題目大意
思路
\(f[u][i]\)表示在u這棵子樹(含u)裡選了i個點的最小代價。
考慮轉移:
假設這棵子樹選擇了q個,那麼這條邊經過了(k-q)*q次。注意的是:揹包的DP必須從大到小或者用輔助陣列。
#include <bits/stdc++.h> #define LL long long const LL INF=0x3f3f3f3f3f3f3f3fll; using namespace std; struct node{ int to, w; }; vector<node> v[50005]; int s[50005]={0}; LL num[50005]={0}; LL f[50005][105]={0}, t[105]; int n, m, k; void dfs(int u, int fa){ f[u][0]=0; if(s[u]==1){ f[u][1]=0; num[u]=1; } for(int r=0; r<v[u].size(); r++){ int to=v[u][r].to, w=v[u][r].w; if(to!=fa){ dfs(to, u); int sum=min(1ll*k ,num[u]+num[to]), mn=min(1ll*k, num[u]); for(int i=0; i<=sum; i++){ t[i]=f[u][i]; } for(int p=0; p<=mn; p++){//其餘子樹i個 for(int q=0; p+q<=sum; q++){//當前子樹j個 t[p+q]=min(t[p+q], f[u][p]+f[to][q]+(q)*(k-q)*1ll*w); } } for(int i=0; i<=sum; i++){ f[u][i]=min(f[u][i], t[i]); } num[u]+=num[to]; } } } int main() { int u, to, w; scanf("%d%d%d", &n, &m, &k); for(int i=1; i<=m; i++){ scanf("%d", &u); s[u]=1; } memset(f, INF, sizeof(f)); for(int i=1; i<=n-1; i++){ scanf("%d%d%d", &u, &to, &w); v[u].push_back(node{to, w}); v[to].push_back(node{u, w}); } dfs(1, 0); printf("%lld\n", f[1][k]); return 0; }
題目二
Wannafly挑戰賽27 藍魔法師(https://ac.nowcoder.com/acm/problem/20811)
題目描述
“你,你認錯人了。我真的,真的不是食人魔。”--藍魔法師
給出一棵樹,求有多少種刪邊方案,使得刪後的圖每個連通塊大小小於等於k,兩種方案不同當且僅當存在一條邊在一個方案中被刪除,而在另一個方案中未被刪除,答案對998244353取模
輸入描述:
第一行兩個整數n,k, 表示點數和限制
2 <= n <= 2000, 1 <= k <= 2000
接下來n-1行,每行包括兩個整數u,v,表示u,v兩點之間有一條無向邊
保證初始圖聯通且合法
輸出描述:
共一行,一個整數表示方案數對998244353取模的結果
示例1
輸入
5 2
1 2
1 3
2 4
2 5
輸出
7
思路
\(f[u][i]\)表示u的所有子樹滿足條件,並且u在的連通塊的大小為i的方案樹。
考慮轉移:
u和to這棵子樹不連通, 那麼to子樹的所有方案都滿足條件。
如果u和to連通,考慮u這棵子樹選擇了p個,to選擇了q個。
時間複雜度分析
每次合併兩棵子樹 u, v 時,需要花費 siz[u] * siz[v] 的代價,所以總複雜度是 \(\sum siz[u] * siz[v]\)。整體來看,這就是說每個點對只會在 LCA 處,對複雜度有 1 的貢獻,所以複雜度是點對數,也就是 O(n^2)。
#include<bits/stdc++.h> #define LL long long using namespace std; const LL mod=998244353; LL f[2005][2005], num[2005]; LL T[2005], siz[2005]; LL k; struct Edge{ int to, nxt; }e[5005]; int cut=0, head[2005]; void addedge(int u, int v){ e[++cut]={v, head[u]}; head[u]=cut; } void DFS(int u, int fa) { f[u][1]=siz[u]=1; for(int i=head[u]; i; i=e[i].nxt) { int x=e[i].to; if(x==fa) continue; DFS(x, u); for(int i=1; i<=k; i++) { T[i]=0; } int n=min(siz[u], k), m=min(k, siz[u]+siz[x]); for (int p = 1; p <= n; p++) { // 列舉已經處理了的包含根節點的子樹對新連通塊的貢獻; for (int q = 0; q <= siz[x] && p + q <= m; q++) // 當前準備處理的子樹對新連通塊的貢獻; // 新聯通塊的大小為 p + q,貢獻的方案數為 f[u][p] * f[to][q]; T[p + q] = (T[p + q] % mod + f[u][p] * f[x][q] % mod) % mod; } for(int i=1; i<=k; i++) { f[u][i]=T[i];// 用T暫存再賦值回去; } siz[u]+=siz[x];// 更新已經處理了的子樹的大小; } for(int i=1; i<=k; i++) {// 將以u為根的所有的可行方案數存在dp[u][0]裡; f[u][0]+=f[u][i]; f[u][0]%=mod; } } int main() { int n, x, y; scanf("%d%lld", &n, &k); for(int i=1; i<n; i++) { scanf("%d%d", &x, &y); addedge(x, y); addedge(y, x); } DFS(1, 0); printf("%lld\n", f[1][0]); return 0; }