1 条题解

  • 0
    @ 2026-5-7 11:51:04

    一道贪心加线段树题。

    题意

    在树上标记点,任意两点的简单距离不小于 dd,求最多能标记多少个点。

    贪心思路

    我们定义,对于一个被标记的点,称与其简单距离小于 dd 的点为被其覆盖的点。贪心思路为每一次找未被覆盖的点中的最深的点标记。

    证明

    在未被覆盖的点中,对于一个的点 vv,若选了他会覆盖最深点 uu,那么选 uu 所覆盖的点一定包含于选 vv 所覆盖的点,所以选 uu 一定优于选 vv

    如何覆盖

    如果我们每选一个点都去暴力覆盖其他点,会超时。考虑用线段树维护一个点是否被覆盖。我们定义 deep(a)deep(a) 表示 aa 点的深度。对于被标记的点 uu,找其祖先 vv,我们要覆盖以 vv 为根,深 d(deep(v)deep(u))1d-(deep(v)-deep(u))-1 的树。但是,线段树只能一下处理一整棵子树,这要怎么办呢?我们发现,我们找点是由深到浅找的,所以如果我们要处理一棵根为 xx,最深深度为 yy 的子树时,等到找点找到深度 yy 的时候再用线段树更新整棵以 xx 为根的子树即可。

    时间复杂度

    判断是否被覆盖:O(nlogn)O(n \log{n})

    覆盖:O(ans×d×logn)O(ans \times d \times \log{n})。又因为 ans×dnans \times d \le n,所以复杂度实际上为 O(nlogn)O(n \log{n})

    总复杂度:O(nlogn)O(n \log{n})

    代码

    #include<bits/stdc++.h>
    using namespace std;
    long long n,k,c,d[200005],ans,dfs[200005],dfss,s[200005],fa[200005],t[800005];
    vector<int> a[200005];//邻接表
    struct xxx
    {
        int x,y;
        bool operator < (const xxx o) const{return y<o.y;}
        bool operator > (const xxx o) const{return y>o.y;}
    };
    priority_queue<xxx> f,p;
    //p存的是覆盖以x为根,最大深度为y的树的操作
    void dfs1(int x)
    {
        dfs[x]=++dfss;//dfs序
        s[x]=1;//子树大小
        f.push((xxx){x,d[x]});
        for(int i=0;i<a[x].size();i++)
        if(a[x][i]!=fa[x])
        {
            int y=a[x][i];
            d[y]=d[x]+1;//深度
            fa[y]=x;//父亲节点
            dfs1(y);
            s[x]+=s[y];
        }
    }
    void update(int num,int l,int r,int x,int y)
    {
        if(x<=l&&r<=y)
        {
            t[num]=1;
            return;
        }
        if(x>r||y<l)
        return;
        update(num*2,l,(l+r)/2,x,y);
        update(num*2+1,(l+r)/2+1,r,x,y);
    }
    bool query(int num,int l,int r,int x)
    {
        if(t[num]==1)
        return false;
        if(l==r)
        return true;
        if(x<=(l+r)/2)
        return query(num*2,l,(l+r)/2,x);
        else
        return query(num*2+1,(l+r)/2+1,r,x);
    }
    int main()
    {
        ios::sync_with_stdio(false);
        cin.tie(0);
        cout.tie(0);
        cin>>n>>k;//k即题目中的d
        for(int i=2;i<=n;i++)
        {
            cin>>c;
            //这里给每一个点的编号都加了1,方便处理
            c++;
            a[c].push_back(i);
            a[i].push_back(c);
        }
        dfs1(1);
        while(!f.empty())
        {
            int x=f.top().x;
            f.pop();
            ans++;
            p.push((xxx){x,d[x]+k-1});
            while(!f.empty())//判断堆头的点是否被覆盖
            {
                while(!p.empty()&&p.top().y>=d[f.top().x])
                {
                    while(p.top().y>d[f.top().x]+1)//小优化(对于影响过深的点)
                    {
                        p.push((xxx){fa[p.top().x],p.top().y-2});
                        p.pop();
                    }
                    if(d[p.top().x]>p.top().y)
                    {
                        p.pop();
                        continue;
                    }
                    if(p.top().x==0)//跳过头了,说明所有点都被覆盖
                    {
                        update(1,1,n,1,n);
                        while(!p.empty())
                        p.pop();
                        break;
                    }
                    update(1,1,n,dfs[p.top().x],dfs[p.top().x]+s[p.top().x]-1);
                    p.push((xxx){fa[p.top().x],p.top().y-2});
                    p.pop();
                }
                if(query(1,1,n,dfs[f.top().x]))
                break;
                else
                f.pop();
            }
        }
        cout<<ans;
    }
    
    • 1

    信息

    ID
    10561
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者