CF2257C 题解

题意简述

我们需要在一棵树上放置最少的“检测器”,使得我们可以确定在这棵树上的某些确定的节点中海狸最终到了哪个点,海狸一定会到达其中一个点。

分析思路

我们可以考虑 树形dp,对于每个节点 \(u\),我们定义 \(u\) 的子节点集合为 \(C_u\)\(m\) 个终点集合为 \(S\),若

\(\sum_{v \in C_u} dp_v + [u \in S] > 1\)

则此时若经过了点 \(u\),则无法确定海狸的终点。
此时我们考虑使用“检测器”使得我们可以确定此时的答案,即

$ dp_u \leftarrow dp_u-dp_v (v \in C_u) $

直到 $ dp_u \leq 1 $,即 此时只存在一种可能

证明

我们该怎么证明呢,以下是简单的证明。

显然,我们的做法尽量让每个检测器尽量向上放置,
这样就可以让更多的检测一起被处理,显然这比直接在每个终点位置添加更加优秀,可以减少检测
例如,只要我们检测到去到了子节点,我们就可以将这个节点想上传递。
多个分叉在同一处可以少检测一个,因为直到其他的都没走,就必定是这个点了

Code

#include <bits/stdc++.h>
#define AC 0
using namespace std;
const int N=1e5+7;
vector<int>G[N];  // 邻接表存图
int n,m,dp[N];
bool is_point[N]; // 是否是可能的终点
vector<int>ans;
void dfs(int u,int fa)
{
    dp[u]=is_point[u];
    for(auto v:G[u])
    {
        if(v==fa)
            continue;
        dfs(v,u);
        dp[u]+=dp[v];
    }
    if(dp[u]>1)
        for(auto v:G[u])
        {
            if(v==fa)
                continue;
            if(dp[v])
                ans.push_back(v),dp[u]-=dp[v];// 在 v 到 u 的边加入“检测器”
            if(dp[u]<=1) 
                break;
        }      
}
void solve()
{
    cin>>n;
    ans.clear();
    for(int i=1;i<=n;i++)
        G[i].clear(),dp[i]=0,is_point[i]=false;
    for(int u=2;u<=n;u++)
    {
        int v;
        cin>>v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        int u;
        cin>>u;
        is_point[u]=true;
    }
    dfs(1,1);
    cout<<ans.size()<<' ';
    for(auto i:ans)
        cout<<i<<' ';
    cout<<'\n';
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int T;
    cin>>T;
    while(T--)
        solve();

    return AC; // 圆满 AC
}

结束

感谢每位读者,这是本蒟蒻第一篇题解

posted @ 2026-08-19 19:57  ForgeTheWorld  阅读(2)  评论(0)    收藏  举报