[JZOJ6096] 森林【倍增】【贪心】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hzj1054689699/article/details/88901224

Description

我们定义对一棵树做一次变换的含义为:当以 1 号节点为根时,交换两个互相
不为祖先的点的子树;
一棵树的权值为对它进行至多一次变换能得到的最大直径长度;
初始时你只有一个节点 1,你需要执行 n-1 个操作,第 i 次操作会给出一个整
数 x,表示新加入第 i+1 号点,并与第 x 号点连一条边。每次操作后输出当前的树
的权值。
强制在线

n<=200000

Solution

这道题专门坑那些数据结构学傻的老年选手(比如我)

先考虑如何变换能得到最大的答案。
在这里插入图片描述

容易发现我们的最终答案一定形如左图,像右图那样选两段不相交的一定是不优的。

更进一步,我们可以发现左边的三叉中的两叉一定构成了原树的直径(有可能往上叉)。

我们对于每一次加入新点,判断能否换掉直径的一个端点,再用剩下的哪一个跟叉出来的分支比一下长度。

计算树上距离用倍增就好了。

并不需要什么LCT
时间复杂度 O ( n log n ) O(n\log n)

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 200005
using namespace std;
int f[N][18],dep[N],n;

int lca(int x,int y)
{
	if(dep[x]>dep[y]) swap(x,y);
	for(int j=dep[y]-dep[x],c=0;j;c++,j>>=1) if(j&1) y=f[y][c];
	for(int j=17;x!=y;)
	{
		while(j&&f[x][j]==f[y][j]) j--;
		x=f[x][j],y=f[y][j];
	}
	return x;
}
 
int dis(int x,int y)
{
	return dep[x]+dep[y]-2*dep[lca(x,y)];
}

int dt(int u,int v,int w)
{
	int p=lca(u,v);
	if(lca(w,p)==p) return min(dis(lca(w,u),w),dis(lca(w,v),w));
	else return dis(w,p);
}
int main()
{
	int tp;
	cin>>tp>>n;
	int ans=0,u=1,v=1,le=0,w=1;
	fo(i,2,n)
	{
		int x,i1=i;
		scanf("%d",&x);
		x^=ans;
		dep[i]=dep[x]+1;
		f[i][0]=x;
		fo(j,1,17) f[i][j]=f[f[i][j-1]][j-1];
		
		if(dis(i,v)>dis(i,u)) swap(u,v);
		int l=dis(i1,u);
		if(l>le) le=l,swap(i1,v);
		
		if(dt(u,v,i1)>dt(u,v,w)) swap(i1,w);
		
		printf("%d\n",ans=le+max(0,dt(u,v,w)-1));
	}
}

猜你喜欢

转载自blog.csdn.net/hzj1054689699/article/details/88901224
今日推荐