小d和送外卖

题目地址

题意:给出一棵树,根节点是1,标记一些点,要求从根节点出发经过所有标记的点后回到1,现在允许删除其中的m个点的标记,求删除m个点的标记后的最小路程

Solution

很厉害的一道题,树形dp+树上背包

考虑dp[i] [j]为从第i个节点出发遍历子树,去掉j个点的标记后,经过所有标记的点后回到i的路程

那么我们可以记录通过背包来转移状态,设去掉i中不包含当前子结点子树的x个点的标记,去掉当前子结点子树中y个点的标记,那么有t[x+y]=max(t[x+y],dp[i] [x]+dp[son] [y])

这还没完,我们记录一下节点i的所有子树和自身中的被标记的点的个数s[i],如果ys[i],那么dp[i] [x]+dp[son] [y]还需要+2,所有有t[x+y]=max(t[x+y],dp[i] [x]+dp[son] [y]+ (ys[son]?2:0))

之后我们把背包中的状态转移到dp[i] [j]里面

我们再用一个res记录一下总路程,如果当前节点的子树有被标记的点,那么肯定要先往子树走,再往子树回来,即res+=2

最后输出res-dp[1] [m]即可

vector<int>e[N];
int dp[N][55];
int vis[N];
int s[N];
int n,m;
int ans=0;
void dfs(int x,int pre)
{

	s[x]=vis[x];
	for(auto it:e[x])
	{
		
		if(it==pre)continue;
		dfs(it,x);
		if(s[it]==0)continue;
		//cout<<it<<"\n";
		int t[150];
		memset(t,0,sizeof(t));
		for(int i=0;i<=min(m,s[x]);i++)
		{
			for(int j=0;j<=min(m,s[it]);j++)
			{
				t[i+j]=max(t[i+j],dp[x][i]+dp[it][j]+(j==s[it]?2:0));
			}
		}
		s[x]+=s[it];
		if(s[it])ans+=2;
		for(int i=0;i<=min(m,s[x]+s[it]);i++)dp[x][i]=t[i];
		//cout<<dp[x][m]<<"\n";
	}
}

void solve()
{
	cin>>n>>m;

	for(int i=1;i<n;i++)
	{
		int u,v;cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}

	int q;cin>>q;
	while(q--)
	{
		int x;cin>>x;
		vis[x]=1;
	}
	dfs(1,0);
	cout<<ans-dp[1][m]<<"\n";
}
posted @ 2023-04-09 21:54  HikariFears  阅读(31)  评论(0)    收藏  举报