「CEOI2013」 Board 题解

题意

\(~~~~\) 给出一棵无穷大的满二叉树,给每一层相邻的节点连边,并给出五种操作。从根节点开始用操作序列描述两个点,求这两个点的最短路。


题解

\(~~~~\) 感觉没有多少人说清楚为什么是往上跳。(虽然这个感性理解一下就行

\(~~~~\) 如果我们把这棵二叉树每个节点像线段树一样编号(每个节点左儿子为该节点编号 \(\times 2\),右儿子为该节点编号 \(\times 2+1\)) ,那么这五种操作依次对应:

  • 移动到左儿子:\(\times 2\)
  • 移动到右儿子:\(\times 2+1\)
  • 移动到父亲节点:\(/2\)
  • 移动到同层左边节点:\(-1\)
  • 移动到同层右边节点:\(+1\)

\(~~~~\) 所以我们最终可以得到两个点所在的编号。现在考虑怎么根据编号求最短路。

\(~~~~\) 首先不难想到我们会把所在较深的点向上跳(不停 \(/2\) )到与另一个节点同层,因为我们若让较浅的节点向下,由于更深的层数横向边只会增多,相当于向下走一步的最好情况是不会增加额外要走的横向边,而向上走的最劣情况是横向边不减少,因此答案比起向上一定不会变优

\(~~~~\) 因此两个节点一定走到较浅的点所在层数及以上的某层后通过该层之间的横向边相遇,考虑枚举走到的层数,那么在该层相遇要走的横向边为两个点跳到的点的编号之差的绝对值。

\(~~~~\) 剩下的部分就像其他题解一样把每个操作改为用二进制高精即可。(或者线段树,平衡树)

\(~~~~\) 同时还要注意,在太深的位置的答案同理一定不会比在更浅的位置更优,因此我们设置一个阈值,从浅到深枚举,当现在枚举层的横向边之差超过该值时直接停止枚举即可。


代码

查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const ll INF=(1<<20);
char s1[100005],s2[100005];
int cnta,cntb,A[100005],B[100005];
inline ll Abs(ll x){return x>0?x:-x;}
void SolveA(int k)//将第 k 位进位或者借位 
{
	A[k-1]+=A[k]/2;A[k]%=2;
	if(A[k]<0) A[k-1]--,A[k]=1;
}
void SolveB(int k)
{
	B[k-1]+=B[k]/2;B[k]%=2;
	if(B[k]<0) B[k-1]--,B[k]=1;
}
void Work()
{
	int len1=strlen(s1+1),len2=strlen(s2+1);
	for(int i=1;i<=len1;i++)
	{
		if(s1[i]=='1') A[++cnta]=0;
		if(s1[i]=='2') A[++cnta]=1;
		if(s1[i]=='U') SolveA(cnta),cnta--;
		if(s1[i]=='L') A[cnta]--;
		if(s1[i]=='R') A[cnta]++;
	}
	for(int i=1;i<=len2;i++)
	{
		if(s2[i]=='1') B[++cntb]=0;
		if(s2[i]=='2') B[++cntb]=1;
		if(s2[i]=='U') SolveB(cntb),cntb--;
		if(s2[i]=='L') B[cntb]--;
		if(s2[i]=='R') B[cntb]++;
	}
	for(int i=cnta;i>=1;i--) SolveA(i);
	for(int i=cntb;i>=1;i--) SolveB(i);
}
int main() {
	scanf("%s %s",s1+1,s2+1);
	Work();
	ll Ans=2e9,tmp=0;
	for(int i=1;i<=min(cnta,cntb)&&Abs(tmp)<INF;i++)//
	{
		tmp=(tmp<<1)+(A[i]-B[i]);
		Ans=min(Ans,cnta-i+cntb-i+Abs(tmp));//注意绝对值
	}
	printf("%lld",Ans);
	return 0;
}

<\details>

原文地址:https://www.cnblogs.com/Azazel/p/14476944.html