洛谷P10135 暨 USACOJan2024S T2 题解
题意简述
给点一棵有 \(n\) 个节点的树,在每个时间点都会在某个节点出现一瓶药水,记 \(p_i\) 为第 \(i\) 个时间点出现药水的节点,定义一次遍历为从 \(1\) 号节点走到任意节点,要求在遍历次数最少的情况下拾取最多数量的药水。
思维路径
首先我们要探讨遍历次数最少的状态是怎样的。由于每一次遍历都是从 \(1\) 号节点开始,我们可以把整棵树看作根为 \(1\) 的有根树。即要求用最少的从根出发的路径覆盖整棵树。
通过画图可以发现每次遍历都从根节点到叶节点是最优的,因为假如存在一次遍历从根节点到非叶节点,那么一定存在一条从根节点到其后代的叶节点的遍历完全覆盖它,一定不是最少的。
那么可以确定总的遍历次数为叶节点的个数,记为 \(nleaf\),也就是说我们只需要考虑前 \(n\) 个时间点出现的药水。注意可能有大于 \(1\) 瓶药水出现在同一个节点,我在这上面挂了挺久。
接着就很明显是一个树上 dp 了。
定义 \(f_i\) 表示以 \(i\) 为根的子树中最多可以拿到 \(f_i\) 瓶药水。
定义 \(lf_i\) 表示以 \(i\) 为根的子树中有 \(lf_i\) 个节点是叶节点
那么 \(f_i\) 的值就是 \(\sum_{u \in son_i}f_u\) 和 \(lf_i\) 中较小的值。最终的答案就是 \(f_1\)
AC 代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=200009;
ll n,a[N],t;
ll hd[N],nE,f[N],in[N],lf[N];
struct edge{
ll to,nxt;
} e[N<<1];
void add(ll u,ll v){
e[++nE]=(edge){v,hd[u]};
hd[u]=nE;
}
void input(){
ios::sync_with_stdio(0); cin.tie(0);
cin>>n;
for(ll i=1;i<=n;i++) cin>>a[i];
for(ll i=1;i<n;i++){
ll u,v;
cin>>u>>v;
in[u]++; in[v]++;
add(u,v);
add(v,u);
}
}
void dfs(ll u,ll fa){
for(ll i=hd[u];i;i=e[i].nxt){
ll v=e[i].to;
if(v==fa) continue;
dfs(v,u);
f[u]+=f[v];
lf[u]+=lf[v];
}
f[u]=min(f[u],lf[u]);
}
void solve(){
for(ll i=2;i<=n;i++)if(in[i]==1){
t++;
lf[i]=1;
}
for(ll i=1;i<=t;i++) f[a[i]]++;
dfs(1,0);
cout<<f[1];
}
int main(){
input();
solve();
return 0;
}
本文来自博客园,作者:lemon-cyy,转载请注明原文链接:https://www.cnblogs.com/lemon-cyy/p/18010674