洛谷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;
}
posted @ 2024-02-07 10:06  lemon-cyy  阅读(30)  评论(0编辑  收藏  举报