小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";
}

浙公网安备 33010602011771号