P13680 未送出的花 [树上背包]
首先注意到一条从\(1\)节点往下的路径权值一定要是单调递减的。这样就可以算出每个节点的权值要作为多少个点的美丽值,将这个值用\(siz\)储存
接着我们发现,对于一个题目要求的\(k\),我们需要选择一些点,使得这些点深度(距离当前子树的根)之和最小(因为每多选一个点就需要多放一个数,这样最终答案就会可能更小),并且\(\sum siz\)不小于\(k\)
于是可以用树上背包,\(dp_{i,j}\)表示已\(i\)为根的子树中,\(siz\)之和为\(j\)的深度之和最小为多少,直接dfs对每个节点转移的复杂度是\(O(n^3)\)
于是我们可以在\(dfs\)序上进行dp,\(dp_{i,j}\)表示\(i-n\)中\(siz\)之和为\(j\)的深度之和最小。这样转移就变成了
$dp_{i,j} = min (dp_{i,j},dp_{i+1,j - siz_i}+1,dp_{i+l_i,j}) $
其中 \(l_i\)表示以 \(i\)为根的子树大小
这样就普通的线性背包复杂度相同
实际上,很多树上背包都可以利用dfs序将复杂度优化掉一维
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int inf = 1e9 + 10;
int n,cnt;
struct node {
int id,w,dep;
bool operator < (const node &a) const {
if(w == a.w) return dep < a.dep;
return w > a.w;
}
};
vector<vector<int> > g,dp;
vector<int> tmp,ans,siz,l,num;
void dfs(int u,int fa) {
num[++cnt] = u;
l[u] = 1;
int len = tmp.size();
len--;
siz[tmp[len >> 1]]++;
for(auto i : g[u]) {
if(i == fa) continue;
tmp.push_back(i);
dfs(i,u);
l[u] += l[i];
tmp.pop_back();
}
}
void solve() {
cin >> n;
cnt = 0;
ans.assign(n + 10,0);
siz.assign(n + 10,0);
num.assign(n + 10,0);
l.assign(n + 10,0);
tmp.clear();
g.assign(n + 10,vector<int>());
dp.assign(n + 10,vector<int>(n + 10,inf));
dp[1][0] = 0;
for(int u,v,i = 1;i < n;i++) {
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
tmp.push_back(1);
dfs(1,0);
for(int i = n;i >= 1;i--) {
dp[i][siz[num[i]]] = 0;
for(int j = siz[num[i]];j <= n;j++)
dp[i][j] = min({dp[i + l[num[i]]][j],dp[i + 1][j - siz[num[i]]] + 1,dp[i][j]});
}
int res = inf;
for(int i = n;i >= 1;i--) {
res = min(res,dp[1][i]);
ans[i] = n - res;
}
for(int i = 1;i <= n;i++) cout << ans[i] << ' ';
cout << '\n';
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t = 1;
cin >> t;
while(t--) solve();
return 0;
}

浙公网安备 33010602011771号