2020牛客暑期多校训练营Ancient Distance(二分+贪心+dfs序+线段树)

Ancient Distance

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

3
1 2
3
1 1

输出

3
2

说明

在这里插入图片描述

备注:

在这里插入图片描述

题目大意

……备注里的话有点……。给定一棵树,树上有一些关键点 ( k e y ) (key) 。要求对于 k { 1 , 2 , . . . , n } k \in\{1,2,...,n\} 个关键点,求所有节点的 A n c i e n t     D i s t a n c e Ancient \,\,\,Distance 的最大值最小是多少。其中, A n c i e n t     D i s t a n c e Ancient \,\,\,Distance 是这个节点在到根节点 r o o t root 的路径上第一个 k e y key 的距离。
备注:如果你有了结果,勇敢地尝试吧

分析

看到题目中说,求最大值的最小,第一个想法就是二分答案。虽然 d a l a o dalao 用分块,但是蒟蒻还是用二分。二分 k k 个节点的最大值,然后验证答案即可。

那么问题来了,怎么验证答案呢???
一下我们分几个问题进行分析:

扫描二维码关注公众号,回复: 11445983 查看本文章

dfs序

首先,我们对这棵树进行dfs序的标号(不计回溯):
在这里插入图片描述
对此,可以跑一次 d f s dfs ,记下 d f n dfn 。然后,我们发现可以对这棵树进行操作——展开变成线段。
在这里插入图片描述
此时,如果我们需要对 2 2 的子树操作,就是对区间 2...6 2...6 进行操作。

线段树

此时,已经展开的树就可以用线段树进行修改和查询了。

但是开头的问题是,怎么验证?我们可以考虑求出每个节点的深度,然后从最深的节点开始,向上 k k 层,就能使得 A n c i e n t     D i s t a n c e Ancient \,\,\,Distance 最大为 k k 。此时,我们将向上 k k 层的节点设为 k e y key ,则整棵子树都有了 A n c i e n t     D i s t a n c e Ancient \,\,\,Distance ,以上图 k = 2 k=2 为例。 A D : A n c i e n t     D i s t a n c e AD:Ancient \,\,\,Distance
在这里插入图片描述
那么如果整棵树上都能合法地放下这样的操作,那么可行。

每棵子树操作后都扔一个标记已经操作,完了后再取消掉,快速避免重复。

为了能快速地修改,查询最大深度,以及记录答案,需要用到线段树。在里面用整体二分。

倍增

还有一个值得考虑的问题,怎么快速地将节点向上跳 k k 层。那么这就要用到倍增了。倍增实际上就是二进制的利用,我们定义一个数组存入 1 , 2 , 4 , 8... 1,2,4,8... ,这样,如果向上跳 10 10 层,只要 2 + 8 2+8 即可,如果向上跳 2050 2050 层,那么只要跳 2048 + 2 2048+2 步,两步解决。

复杂度计算

二分 log n \log n ,线段树查询修改 log n \log n ,遍历 k k n n ,加上每个子树都要一次,最多 n k \frac{n}{k}
PS: n k = 1 1 + 1 2 + 1 3 + 1 4 + 1 5 + . . . + 1 n \frac{n}{k}=\frac{1}{1}+\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+\frac{1}{5}+...+\frac{1}{n}
       1 1 + 1 2 + 1 2 + 1 4 + 1 4 + 1 4 + 1 4 . . . + 1 n 2 \qquad\,\,\,\,\,\,\le\frac{1}{1}+\frac{1}{2}+\frac{1}{2}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}+\frac{1}{4}...+\frac{1}{比n大的最小的2的次幂}
       log n \qquad\,\,\,\,\,\,\le\log n
所以 n k \frac{n}{k} 可以看做 log n \log n
故复杂度为 O ( n log 3 n ) O(n\log^3n)

代码

#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=2e5+10;
int dfn[MAXN],vis[MAXN],dp[MAXN],dep[MAXN],ans[MAXN],f[MAXN];
//这些变量名都可以顾名思义
int num=0,mxdep=0,n;
vector<int> e[MAXN];//边
void dfs(int pos,int fa){
    dfn[++num]=pos;//反着记,dfn为num的编号为pos
    for(int i=0;i<e[pos].size();i++){
        int s=e[pos][i];if(s==fa) continue;
        vis[s]=1; dep[s]=dep[pos]+1; mxdep=max(mxdep,dep[s]);//计算deep和deepest
        dfs(s,pos); vis[s]=0;
    }
}
void upQAQ(int pl,int pr,int l,int r){//QAQ看了半天才有点懂,滚来写题解了,所以取名upQAQ
    if(pl>pr||l>r) return;
    if(l==r){
        for(int i=pl;i<=pr;i++) ans[i]=l;
        return;
    }
    for(int i=0;i<=n;i++) dp[i]=dep[i];
    int mid=(pl+pr)>>1;ans[mid]=0;
    for(int i=n;i>=1;i--){
        int pos=dfn[i];
        if(dp[pos]==dep[pos]+mid||i==1) ans[mid]++,dp[pos]=-1;
        dp[f[pos]]=max(dp[f[pos]],dp[pos]);
    }
    upQAQ(pl,mid-1,ans[mid],r);
    upQAQ(mid+1,pr,l,ans[mid]);
}//pl pr二分l r线段树的左右端点
//据说这是另一个线段树的写法
int main()
{
    while(~scanf("%d",&n)){
        num=mxdep=0;
        for(int i=0;i<=n;i++) e[i].clear();
        for(int i=2;i<=n;i++)
            scanf("%d",&f[i]),
            e[f[i]].push_back(i),
            e[i].push_back(f[i]);//题目里存边的方式非常玄幻
        dep[1]=0;
        dfs(1,-1);
        upQAQ(0,mxdep,1,n);
        ll anans=0;
        for(int i=1;i<=mxdep;i++)
        	anans+=1ll*(ans[i-1]-ans[i])*i;
        printf("%lld\n",anans);
    }
}

END

无言~

猜你喜欢

转载自blog.csdn.net/zhangchizc/article/details/107535404