题目描述
输入描述:
输出描述:
示例1
输入
3
1 2
3
1 1
输出
3
2
说明
备注:
题目大意
……备注里的话有点……。给定一棵树,树上有一些关键点
。要求对于
个关键点,求所有节点的
的最大值最小是多少。其中,
是这个节点在到根节点
的路径上第一个
的距离。
备注:如果你有了结果,勇敢地尝试吧
分析
看到题目中说,求最大值的最小,第一个想法就是二分答案。虽然 用分块,但是蒟蒻还是用二分。二分 个节点的最大值,然后验证答案即可。
那么问题来了,怎么验证答案呢???
一下我们分几个问题进行分析:
dfs序
首先,我们对这棵树进行dfs序的标号(不计回溯):
对此,可以跑一次
,记下
。然后,我们发现可以对这棵树进行操作——展开变成线段。
此时,如果我们需要对
的子树操作,就是对区间
进行操作。
线段树
此时,已经展开的树就可以用线段树进行修改和查询了。
但是开头的问题是,怎么验证?我们可以考虑求出每个节点的深度,然后从最深的节点开始,向上
层,就能使得
最大为
。此时,我们将向上
层的节点设为
,则整棵子树都有了
,以上图
为例。
那么如果整棵树上都能合法地放下这样的操作,那么可行。
每棵子树操作后都扔一个标记已经操作,完了后再取消掉,快速避免重复。
为了能快速地修改,查询最大深度,以及记录答案,需要用到线段树。在里面用整体二分。
倍增
还有一个值得考虑的问题,怎么快速地将节点向上跳 层。那么这就要用到倍增了。倍增实际上就是二进制的利用,我们定义一个数组存入 ,这样,如果向上跳 层,只要 即可,如果向上跳 层,那么只要跳 步,两步解决。
复杂度计算
二分
,线段树查询修改
,遍历
要
,加上每个子树都要一次,最多
。
PS:
所以
可以看做
。
故复杂度为
。
代码
#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
无言~