树的直径,LCA复习笔记

前言

复习笔记第6篇。

求直径的两种方法

树形DP:

dfs(y); ans=max( ans,d[x]+d[y]+w[i] ); d[x]=max( d[x],d[y]+w[i] );

               int dis=dfs( v,u )+1;
                if ( f[u]<dis ) g[u]=f[u],f[u]=dis;
                else if ( g[u]<dis ) g[u]=dis;
        ans=max( ans,f[u]+g[u]+1 );
        return f[u];

两次 bfs/dfs:

从任意点出发,找到最远点l;

从 l 出发,找到最远点 r. \(l\to r\) 即为所求。

0——P4408 [NOI2003]逃学的小孩

link

题意

\(n\)\(m\) 边的带权无向图,任意两点之间有且仅有一条通路。有三个点:A,B,C,从 C 出发,先去 AB 中较近的一个,如果没找到,再去另一个,不给出具体的 ABC,问最坏情况下要多长时间。

思路

题目要求就是在一棵树上找到3个点 \(A\)\(B\)\(C\)\(AB+BC\) 最大,同时要满足 \(AC>AB\)

由于要最大化这个距离,一个很明显的想法就是让其中一条成为直径,设为 \(AB.\) 然后再找另一条 \(BC\) 即可。但是要满足 \(AC>AB\) ,所以就是:先找直径 \(AB\) ,然后找一点 \(C\) 使得 \(min(AC,BC)\) 取得最大值,\(ans=\) 直径 \(+min(AC,BC)=\) 直径 \(+BC.\)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+10;
struct edge
{
	int to,nxt;ll val;
}e[N*10];
int n,m,head[N],tot=0,vis[N];
ll dis[N],dis2[N];

void add( int u,int v,ll w )
{
	tot++; e[tot].to=v; e[tot].nxt=head[u]; e[tot].val=w; head[u]=tot;
}

int bfs( int S )
{
	memset( dis,0,sizeof(dis) ); memset( vis,0,sizeof(vis) );
	queue<int> q; 
	while ( !q.empty() ) q.pop();
	q.push( S ); vis[S]=1; int res=0,num=0;
	while ( q.size() )
	{
		int u=q.front(); q.pop();
		for ( int i=head[u]; i; i=e[i].nxt )
		{
			int v=e[i].to;
			if ( vis[v] ) continue;
			vis[v]=1; dis[v]=dis[u]+e[i].val; q.push( v );
			if ( dis[v]>res ) res=dis[v],num=v;
		}
	}
	return num;
}

int main()
{
	scanf( "%d%d",&n,&m );
	for ( int i=1; i<=m; i++ )
	{
		int u,v; ll w; scanf( "%d%d%lld",&u,&v,&w );
		add( u,v,w ); add( v,u,w );
	}

	int l=bfs(1),r=bfs(l); ll ans=dis[r],res=0;
	for ( int i=1; i<=n; i++ )
		dis2[i]=dis[i];
	bfs( r );
	for ( int i=1; i<=n; i++ )
		res=max( res,min(dis[i],dis2[i]) );
	
	printf( "%lld",ans+res );
}

1——P2491 [SDOI2011]消防

link

题意

\(n\) 个城市,任意两个都连通且有唯一路径,每条连通两个城市的道路的长度为 \(z_i\). 在一条边长度和不超过 \(s\) 的路径(两端都是城市)上建立消防枢纽,要求其他所有城市到这条路径的距离的最大值最小。求枢纽位置。

思路

每一个点到树上最远的点一定在直径上.所以可以直接枚举直径上的点,然后在找直径上的点到其他点的距离最大是多少.一个很显然的想法是,在直径上取的距离越大越优,那么可以直接枚举起点。

但是 \(N^2\) 复杂度还是不够。显然,答案具有单调性(不超过 s 的情况下越长越好),那么可以通过双指针,单调队列优化,\(O(n)\) 实现这个过程。当然也可以二分不过要带一个 \(\log.\)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll N=3e5+10;
const ll inf=1e15+10;
struct edge
{
        ll fro,to,nxt; ll val;
}e[N<<1];
ll n,s,tot,head[N],dis[N],pre[N],bet[N],l,r;
ll ans=inf,sum[N],dis1[N],que[N]={inf},t=0,h=0;

void add( ll u,ll v,ll w )
{
        e[++tot].fro=u; e[tot].to=v; e[tot].val=w; e[tot].nxt=head[u]; head[u]=tot;
}

void dfs( ll u,ll fa,ll sum,bool sav )
{
        if ( sav ) pre[u]=fa,bet[u]=sum;
        dis[u]=dis[fa]+sum;
        for ( ll i=head[u]; i; i=e[i].nxt )
                if ( e[i].to!=fa ) dfs( e[i].to,u,e[i].val,sav );
}

void get_path()
{
        dfs( 1,0,0ll,0 ); ll mx=0;
        for ( ll i=1; i<=n; i++ )
                if ( dis[i]>mx ) l=i,mx=dis[i];
        dfs( l,0,0ll,1 ); mx=0;
        for ( ll i=1; i<=n; i++ )
                if ( dis[i]>mx ) r=i,mx=dis[i];
}

void bfs()
{
        memset( dis,63,sizeof(dis) ); queue<ll> q,fro;
        for ( ll i=r; i; i=pre[i] )
                q.push(i),fro.push(i),dis[i]=0;
        while ( !q.empty() )
        {
                ll v=q.front(),u=fro.front(); q.pop(); fro.pop();
                for ( ll i=head[v]; i; i=e[i].nxt )
                        if ( dis[e[i].to]>=inf )
                        {
                                dis[e[i].to]=dis[v]+e[i].val;
                                dis1[u]=max( dis1[u],dis[e[i].to] );
                                q.push( e[i].to ); fro.push( u );
                        }
        }
}

int main()
{
        scanf( "%lld%lld",&n,&s );
        for ( ll i=1,u,v,w; i<n; i++ )
                scanf( "%lld%lld%lld",&u,&v,&w ),add( u,v,w ),add( v,u,w );
        
        get_path(); bfs();
        pre[n+1]=r;
        for ( ll i=n+1; i; i=pre[i] )
                sum[pre[i]]=sum[i]+bet[i];
        for ( ll L=r,R=r; L && R!=l; L=pre[L] )
        {
                ll las=R; h++;
                while ( sum[R]-sum[L]<=s && R )
                {
                        las=R; R=pre[R];
                        if ( R && sum[R]-sum[L]<=s )
                        {
                                while ( dis1[R]>=que[t] && t>=h ) t--;
                                que[++t]=dis1[R];
                        }
                }
                if ( R==0 || sum[R]-sum[L]>s ) R=las;
                ll tmp=max( sum[L],sum[l]-sum[R] ); tmp=max( tmp,que[h] );
                ans=min( tmp,ans );
        }

        printf( "%lld",ans );

        return 0;
}

2——P2610 [ZJOI2012]旅游

link

题意

T国的国土可以用一个凸N边形来表示,包含 \(N-2\) 个城市,每个城市都是顶点为 \(N\) 边形顶点的三角形,两人的旅游路线可以看做是连接N个顶点中不相邻两点的线段。问一路能经过最多多少城市。

一个城市被当做经过当且仅当其与线路有至少两个公共点。

思路

很巧妙的一道题。(不愧是ZJOI)

三角剖分是个很有意思的信息。不是让你想递推啊喂

考虑什么样的城市是不满足三角剖分的。会发现,不可能存在一些城市围成一圈(这样有一个点就会在内部而不是端点),所以如果将相邻的城市连边,是不可能存在环的。没有环,又是连通的,那么就是树了。

问题就转化成了求树的直径,裸题。

然而事实上,建图有亿点麻烦...但是非常幸运的是,由于这道题是三角形,所以肯定是二叉树,那么我们有pair!我们有 map!我们有STL!重点是我们有O2!

于是这道题就做完了。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
struct edge
{
        int to,nxt;
}e[N<<1];
int head[N],tot=0,n,s[N][3],ans,g[N],f[N];
map<pair<int,int>,int> mp;

void add( int u,int v )
{
        e[++tot].to=v; e[tot].nxt=head[u]; head[u]=tot;
        e[++tot].to=u; e[tot].nxt=head[v]; head[v]=tot;
}

int dfs( int u,int fa )
{
        for ( int i=head[u]; i; i=e[i].nxt )
        {
                int v=e[i].to;
                if ( v==fa ) continue;
                int dis=dfs( v,u )+1;
                if ( f[u]<dis ) g[u]=f[u],f[u]=dis;
                else if ( g[u]<dis ) g[u]=dis;
        }
        ans=max( ans,f[u]+g[u]+1 );
        return f[u];
}

void build( int i,int j,int u )
{
        pair<int,int> pr=make_pair( i,j );
        if ( mp[pr] ) add( u,mp[pr] );
        else mp[pr]=u;
}

int main()
{
        scanf( "%d",&n );
        for ( int i=1; i<=n-2; i++ )
        {
                scanf( "%d%d%d",&s[i][0],&s[i][1],&s[i][2] ); sort( s[i],s[i]+3 ); 
                build( s[i][0],s[i][1],i ); build( s[i][1],s[i][2],i ); build( s[i][0],s[i][2],i );
        }

        dfs( 1,0 );
        printf( "%d",ans );

        return 0;
}

3——P3629 [APIO2010]巡逻

link

题意

\(n\) 个村庄,编号为 \(1, 2, ..., n\) 。有 \(n – 1\) 条道路连接着这些村 庄,从任何一个村庄都可以到达其他任一个村庄。道路长度均为 1。 巡警车每天要到所有的道路上巡逻。警察局设在编号为 \(1\) 的村庄里,每天巡警车总是从警察局出发又回到警察局。

在这些村庄之间建 \(K\) 条新的道路, 可以连接任意两个村庄。每天巡警车必须 经过新建的道路正好一次. 求最小的巡逻距离。

思路

考虑逐条加边。

如果不加边,那么答案显然是 \(2(n-1)\).

如果加一条边,由于必须经过恰好一次,所以在沿着新的道路 \((u,v)\) 走了一次之后,要返回 \(u\) ,必须沿着树上的环的另一半再走一遍,那么这时候 \(u\to v\) 的路径只需要走一次,所以 \(ans=2(n-1)-L-+1.\)

再加一条边,如果环没有重叠,那么按照一条的情况处理即可。否则,重叠部分不会被走过,所以还要走一次,又变成了需要走两次的边。

总结两种情况,得到算法:

  1. 找一遍直径,边权取反,长度为 \(L_1\)
  2. 再求直径,得到 \(L_2\)
  3. \(ans=2(n-1)-(L_1-1)-(L_2-1)\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct edge
{
        int to,nxt,val;
}e[N<<1];
int n,k,tot=0,mx,head[N],dis[N],pre[N],f[N];
bool vis[N];
queue<int> q;

void add( int u,int v,int w )
{
        e[++tot].to=v; e[tot].val=w; e[tot].nxt=head[u]; head[u]=tot;
}

int bfs( int s )
{
	memset( dis,0x3f,sizeof(dis) );
	q.push( s ); dis[s]=pre[s]=0;
	while ( q.size() )
	{
		int t=q.front(); q.pop();
		for ( int i=head[t]; i; i=e[i].nxt )
			if ( dis[e[i].to]==0x3f3f3f3f )
				dis[e[i].to]=dis[t]+e[i].val,pre[e[i].to]=i,q.push( e[i].to );
	}
	int res=1;
	for ( int x=1; x<=n; x++ )
		if ( dis[x]>dis[res] ) res=x;
	return res;
}

void dp( int x )
{
	vis[x]=1; 
	for ( int i=head[x]; i; i=e[i].nxt )
		if ( !vis[e[i].to] )
		{
                        dp( e[i].to );
                        mx=max( mx,f[e[i].to]+f[x]+e[i].val );
                        f[x]=max( f[x],f[e[i].to]+e[i].val ); 
		}
}

int main()
{
	memset( head,0,sizeof(head) ); tot=1;

	scanf( "%d%d",&n,&k );
	for ( int i=1,u,v; i<n; i++ )
		scanf( "%d%d",&u,&v ),add( u,v,1 ),add( v,u,1 );
        
        int l=bfs( 1 ); l=bfs(l);
        int L1=dis[l],fl=1; mx=0;
        if ( k==2 )
        {
                for ( ; pre[l]; l=e[pre[l]^1].to )
                        e[pre[l]].val=e[pre[l]^1].val=-1;
                dp( 1 ); fl=2;
        }
        printf( "%d",2*(n-1)-L1-mx+fl );

        return 0;
}

4——P4381 [IOI2008]Island

link

题意

\(N\) 个岛屿组成,从每个岛屿 \(i\) 出发向另外一个岛屿建了一座长度为 \(L_i\) 的桥,可以双向行走。同时,每对岛屿之间都有一艘专用的往来两岛之间的渡船。你希望经过的桥的总长度尽可能长,但受到以下的限制:

  • 可以自行挑选一个岛开始游览。
  • 任何一个岛都不能游览一次以上。
  • 任何时间都可以由当前所在的岛 \(S\) 去另一个从未到过的岛 \(D\)。从 \(S\)\(D\) 有如下方法:
    • 步行:仅当两个岛之间有一座桥,桥长会累加到步行总距离中。
    • 渡船:仅当没有任何桥和以前使用过的渡船的组合可以由 \(S\) 走到 \(D\) (检查是否可到达时应该考虑所有路径,包括经过曾游览过的岛)。

注意,你不必游览所有的岛,也可能无法走完所有的桥。

给定 \(N\) 座桥以及它们的长度,按照上述的规则,计算你可以走过的桥的长度之和的最大值。

思路

每个岛屿一座桥,\(n\) 个岛屿 \(n\) 座桥……诶,不是树?出大问题

于是你不幸地发现,这是基环树,而且还是基环树森林!不过没有关系。由题意可知,如果乘船离开一棵基环树就不能再回来了。(因为回来要坐船,然而你坐过了,所以可达性成立,你就不能坐船了)

于是题目就变成了,求所有基环树的直径之和。

首先,找出基环树的环。然后,对于直径,显然分成两部分,要么在去掉环的某棵子树内,要么是环上一段距离加上两棵不同子树内的距离。

于是可以先预处理出环上每个点在去掉环的前提下,子树内的直径。然后把环展开,分顺时针和逆时针两种情况讨论,每次的 \(res=dis(i,j)+d[i]+d[j]\) ,取max 即可。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
int n,m,cnt=0,dfn[N],fa[N],pre[N],a[N<<1];
int head[N],tot=1,q[N<<1];
ll ans=0,d[N<<1],f[N],b[N<<1],res;
bool vis[N];

struct edge
{
	int nxt,to,val;
}e[N<<1];

void add( int u,int v,int w )
{
	e[++tot]=(edge){head[u],v,w}; head[u]=tot;
}

void bfs1( int u )		//找环 
{
	int tail=0; q[++tail]=u;
	while ( tail )
	{
		int he=q[tail--]; dfn[he]=++cnt;
		for ( int i=head[he]; i; i=e[i].nxt )
		{
			int v=e[i].to;
			if ( i==(pre[he]^1) ) continue;
			if ( !dfn[v] ) { fa[v]=he,pre[v]=i,q[++tail]=v; }
			else if ( !m )
			{
				int p=he; 
				for ( ; p!=v; p=fa[p] )
					a[++m]=p,d[m]=e[pre[p]].val,vis[p]=1;
				a[++m]=v; d[m]=e[i].val; vis[v]=1;
			}
		}
	}
}

ll bfs( int x )
{
	int h=1,t=0; q[++t]=x;
	while ( h<=t )
	{
		int u=q[h++];
		for ( int i=head[u]; i; i=e[i].nxt )
		{
			int v=e[i].to;
			if ( vis[v] ) continue;
			fa[v]=u; vis[v]=1; q[++t]=v; pre[v]=e[i].val;
		}
	}
	for ( int i=t; i>1; i-- )
	{
		int v=q[i],u=fa[v],w=pre[v];
		res=max( res,f[u]+f[v]+w );
		f[u]=max( f[u],f[v]+w );
	}
	return f[x];
}

void solve( int x )
{
	m=res=0; bfs1( x );
	if ( !m )  { bfs( x ); ans+=res; return; }
	reverse( a+1,a+1+m ); reverse( d+1,d+1+m );		//反转
	for ( int i=1; i<=m; i++ )
		b[i]=bfs( a[i] );
	for ( int i=m+1; i<=m*2; i++ )		//复制,展环成链 
		a[i]=a[i-m],b[i]=b[i-m],d[i]=d[i-m];
	for ( int i=1; i<=m*2; i++ )
		d[i]+=d[i-1];
	int h=0,t=0; q[0]=0;
	for ( int i=1; i<=m*2; i++ )
	{
		while ( h<=t && i-q[h]+1>m ) h++;
		if ( h<=t ) res=max( res,b[i]+b[q[h]]+d[i]-d[q[h]] );
		while ( h<=t && b[q[t]]-d[q[t]]<=b[i]-d[i] ) t--;
		q[++t]=i;
	}
	ans+=res;
}

int main()
{
	scanf( "%d",&n );
	for ( int i=1,v,w; i<=n; i++ )
		scanf( "%d%d",&v,&w ),add( v,i,w ),add( i,v,w );
		
	for ( int i=1; i<=n; i++ )
		if ( !dfn[i] ) solve( i );
	
	printf( "%lld\n",ans );
}

以上是树的直径部分。

5——P5021 赛道修建

link

题意

有一棵带边权的树,在树上选出 \(m\) 条互不相交的链(点可以重合,但是边不能重合),使得 \(m\) 条链中最短的链最长。

思路

看到这个很容易想到总体思路是二分。

考虑如何判定。然后发现这道题其实藏了一个贪心:对于一棵子树内的所有链,在有最多的儿子对答案做出贡献的前提下,最大化 \(f_i\) 的值。由于 一个点的 \(f\) 最多对答案产生 \(1\) 的贡献,所以让儿子更少贡献,转移更大的 \(f_i\) 不会变优。

那么正解就来了。首先二分出一个 \(mid\) 。然后对于一棵子树,令 \(f_i\) 为以 \(i\) 为根的子树中,最优的不完整的链长(完整的根据上面的贪心分析,已经 \(\ge mid\) ,贡献到答案里去了;不完整就是还要和别的链拼接的)。

暂时不考虑根节点 \(i\) ,对于 \(i\) 所有的儿子,如果能单独贡献则直接计入答案。否则,尝试两两合并这些子链(见题意,点是可以重合的,在根节点合并多少都没有关系),如果长度 \(\ge mid\) 就计入答案。如果都不行,那就在这些剩余的链中选取最长的一条,计入 \(f_i\) ,尝试往上走。于是就可以把所有子节点的 \(f_j\) 排序,贪心找最大的匹配数(能匹配的几条中最小的一个),然后把剩下的转移给 \(f_i\) 即可。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e4+10;
struct edge
{
	int to,nxt; ll val;
}e[N<<1];
int head[N],tot=0,n,m;
ll f[N],subans[N];
vector<int> son[N];

void add( int u,int v,ll w )
{
	e[++tot].to=v; e[tot].nxt=head[u]; e[tot].val=w; head[u]=tot;
}

int get_ans( int u,int pos,int cnt,ll x )
{
	int res=0,l=0;
	for ( int r=cnt-1; r; r-- )
	{
		r-=(r==pos);
		while ( l<r && son[u][l]+son[u][r]<x ) ++l;
		l+=(l==pos);
		if ( l>=r ) break;
		res++; l++;
	}
	return res;
}

void dfs( int u,int fa,ll x )
{
	f[u]=subans[u]=0; son[u].clear();
	for ( int i=head[u]; i; i=e[i].nxt )
	{
		int v=e[i].to;
		if ( v==fa ) continue;
		dfs( v,u,x ); f[v]+=e[i].val;
		if ( f[v]>=x ) subans[u]++;
		else son[u].push_back( f[v] );
	}
	int cnt=son[u].size(); sort( son[u].begin(),son[u].end() );
	int l=0,r=cnt,sub=0,res;
	for ( int r=cnt-1; r; r-- )		//配对
	{
		while ( l<r && son[u][l]+son[u][r]<x ) l++;
		if ( l>=r ) break;
		sub++; l++;
	}
	subans[u]+=sub;
	if ( sub*2==cnt ) return;
	l=0; r=cnt-1;
	while ( l<=r )		//二分找最大的一个mid,使得首先满足子树要求
	{
		int mid=(l+r)>>1;
		int tmp=get_ans( u,mid,cnt,x );
		if ( tmp==sub ) res=mid,l=mid+1;
		else r=mid-1;
	}
	f[u]=son[u][res];	//记入f[i]
}

bool check( ll x )
{
	int res=0; dfs( 1,0,x );
	for ( int i=1; i<=n; i++ )		//每个点的贡献
		res+=subans[i];
	return res>=m;
}

int main()
{
	ll l=0,r=0;
	scanf( "%d%d",&n,&m );
	for ( int i=1; i<n; i++ )
	{
		int u,v; ll w; scanf( "%d%d%lld",&u,&v,&w );
		add( u,v,w ); add( v,u,w ); r+=w;
	}

	r/=(ll)m; ll ans=0;
	while ( l<=r )
	{
		ll mid=(l+r)>>1;
		if ( check(mid) ) ans=mid,l=mid+1;
		else r=mid-1;
	}

	printf( "%lld",ans );

	return 0;
}

6——P1852 跳跳棋

link

题意

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。棋盘上有3颗棋子,分别在 \(a,b,c\) 这三个位置。我们要通过最少的跳动把他们的位置移动成 \(x,y,z\) 。(棋子是没有区别的)

跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。

判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

思路

神仙题……非常巧妙地建模。只能说:女少口阿

首先,对于中轴棋子为 \(b\) (中间那个)的情况,显然一直往中间跳可以一直减小范围,直到不能跳为止。这时候就得到了一个非常有用的“Basic” 状态,也就是“根状态”(这怎么跟某道字符串手玩题这么像啊)

然后把 \(b\) 往左右跳的情况看成左右节点状态,那么所有状态构成了一棵二叉树。对于棋盘上所有的 \(a,b,c\) ,状态构成了一个森林。

那么,如果 \((a,b,c)\to (x,y,z)\) ,首要条件是在同一棵树上。这样第一问就解决了。

考虑状态怎么去树根。利用 LCA 的思想,把两个状态到根的距离调整到一样,然后二分向上的步数,最后找到一个 \(L\) 使得两个状态向上 \(L\) 步相遇,那么总答案就是 高度差加上二分答案的两倍。

代码

#include <bits/stdc++.h>
using namespace std;
const int inf=1e9+7;
int sx,sy,sz,dep,mx;

void init( int &x,int &y,int &z )
{
	x+=inf; y+=inf; z+=inf;
	if ( y>z ) swap( y,z );
	if ( x>y ) swap( x,y );
	if ( y>z ) swap( y,z );
}

void dfs( int x,int y,int z,int step )
{
	int del1=y-x,del2=z-y;
	if ( step==mx || del1==del2 ) { sx=x,sy=y,sz=z; dep=step; return; }
	if ( del1>del2 )
	{
		swap( del1,del2 ); int del=del2/del1;
		if ( del2%del1==0 ) del--;
		if ( step+del<=mx ) dfs( x,y-del*del1,z-del*del1,step+del );
		else dfs( x,y-(mx-step)*del1,z-(mx-step)*del1,mx );
	}
	else
	{
		int del=del2/del1;  del-=(del2%del1==0);
		if ( step+del<=mx ) dfs( x+del*del1,y+del*del1,z,step+del );
		else dfs( x+(mx-step)*del1,y+(mx-step)*del1,z,mx );
	}
}

int main()
{
	int x,y,z,a,b,c;
	scanf( "%d%d%d",&a,&b,&c ); init( a,b,c );
	scanf( "%d%d%d",&x,&y,&z ); init( x,y,z );
	
	mx=inf;
	dfs( a,b,c,0 ); int sa=sx,sb=sy,sc=sz,sd=dep;
	dfs( x,y,z,0 );
	if ( sx!=sa || sy!=sb || sz!=sc ) { printf( "NO" ); return 0; }
	printf( "YES\n" );
//------------query1-------------------
	int ans=0;
	if ( sd>dep )
	{
		ans=sd-dep; mx=sd-dep;
		dfs( a,b,c,0 ); a=sx; b=sy; c=sz;
	}
	if ( sd<dep )
	{
		ans=dep-sd; mx=dep-sd;
		dfs( x,y,z,0 ); x=sx,y=sy,z=sz;
	}
	
	int l=0,r=inf;
	while ( l<=r )
	{
		mx=(l+r)>>1;
		dfs( a,b,c,0 ); sa=sx,sb=sy,sc=sz;
		dfs( x,y,z,0 );
		if ( sa!=sx || sb!=sy || sc!=sz ) l=mx+1;
		else r=mx-1;
	}

	printf( "%d",(l<<1)+ans );
}

Last

To be continue...

posted @ 2020-11-02 20:10  MontesquieuE  阅读(278)  评论(0编辑  收藏  举报