1. 程式人生 > 實用技巧 >樹的直徑,LCA複習筆記

樹的直徑,LCA複習筆記

樹的(直徑,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...