https://www.luogu.org/problemnew/show/P2279

 

和刘汝佳白书第一章一道例题差不多。

确实是无根树,但是我们可以把它"看做"有根树,这样并不会影响这棵树的性质,这棵树该是什么样还是什么样的,这只是一个看待的方式,这样我们更容易去思考。

考虑当前深度最大的结点,肯定要有一个消防局去覆盖它,这个消防局可以是三个位置:他的父亲,他的兄弟,他的爷爷。

显然,他的父亲和兄弟可以覆盖到的点全部可以被他的爷爷覆盖到,并且他的爷爷可能覆盖到更多的结点,因此选他的爷爷做消防局。

那么,做法就有了:先预处理出各个深度的点集,然后从深到浅,遇到未覆盖结点时取它的爷爷做消防局,并以它的爷爷为起点dfs2层,标记掉覆盖的结点。

这样的复杂度比n^2要小,但比n要大得多,我是不会算。

洛谷排名第一的题解给了一种更好的实现https://www.luogu.org/blog/contributation/solution-p2279 ,非常简短。

这是我的代码:

#include<bits/stdc++.h>
using namespace std;
#define maxn 1000+100

int n,coverd[maxn],fa[maxn],ans;
vector<int> G[maxn],nodes[maxn];

void dfs(int u,int f,int d)
{	
    fa[u]=f;
    nodes[d].push_back(u);
    for(int i=0;i<G[u].size();i++)if(G[u][i]!=f)
        dfs(G[u][i],u,d+1);
}

void dfs2(int u,int f,int d)
{	
    coverd[u]=1;
    for(int i=0;i<G[u].size();i++)if(G[u][i]!=f&&d<=1)
        dfs2(G[u][i],u,d+1);
}

void solve()
{	
    for(int i=n-1;i>=0;i--)
    {
        for(int j=0;j<nodes[i].size();j++)
        {
            int u=nodes[i][j];
            if(coverd[u])continue;
            if(fa[u]!=-1)u=fa[u];
            if(fa[u]!=-1)u=fa[u];
            ans++;
            dfs2(u,-1,0);
        }
    }
}

int main()
{
	freopen("input.in","r",stdin);
    int x;
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        cin>>x;
        G[i].push_back(x);
        G[x].push_back(i);
    }
    dfs(1,-1,0);
    solve();
    cout<<ans<<endl;
    return 0;
}


这是那位大佬的代码我小改一点的:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define N 1020
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
int n,b[N],f[N],d[N],o[N],ans,u,v,w;
bool cmp(int x,int y){return d[x]>d[y];}
int main(){
    scanf("%d",&n);b[1]=1,o[1]=o[0]=N;
    FOR(i,2,n) scanf("%d",&f[i]),d[i]=d[f[i]]+1,b[i]=i,o[i]=N;
    sort(b+1,b+n+1,cmp);
    FOR(i,1,n){
        v=b[i],w=f[v],u=f[f[v]];
        o[v]=min(o[v],min(o[w]+1,o[u]+2));
        if(o[v]>2){
            o[u]=0,ans++;
            o[f[u]]=1,o[f[f[u]]]=2;
        }
    }printf("%d",ans);
}