P5513 [CEOI2013] Board 题解

发布时间 2023-12-20 09:04:29作者: jr_inf

赛时(模拟赛)乱加优化写挂了,爬来写题解。

发现点的深度和路径长度都非常大,而且一个点有多种方式到达,考虑先用统一的方式存储两个点的位置,再进行求解。

存储

为了更好地表示当前的位置,考虑对每个点编号。首先想到类似线段树的编号方法:初始点编号为 \(1\),设当前点编号为 \(x\),则左儿子编号为 \(2x\),右儿子编号为 \(2x+1\)。设当前点为 \(x\),可以将五种操作变为以下形式:

  • 1\(x:=2x\)
  • 2\(x:=2x+1\)
  • U\(x:=\frac{x}{2}\)
  • L\(x:=x-1\)
  • R\(x:=x+1\)

不难发现,只要用一个高精度就可以存下 \(x\) 的值,而且非常好维护。但是每次更新都跑一遍进退位的时间是 \(O(D^2)\) 的,可以在每次退位(\(U\) 操作)的时候才更新当前位对上一位的贡献,最后再扫一遍一起更新,即可做到 \(O(S)\)

求解

\(A\) 的位置为 \(a\)\(B\) 的位置为 \(b\)

最优情况下,\(A,B\) 应该先向上移动到同一层(也可能不动),再通过横向移动相遇。比如题目中的 \(B\),对它执行 LU 和执行 U 后的位置相同,说明现在的 U 可能使得上次操作的 LR 失效,所以先执行 U 是更优的。

我们可以通过从小到大枚举移动到哪一层,这样两点在本层的距离也可以 \(O(1)\) 求了。答案显然不大于 \(2*10^6\),记得在适当位置剪枝以保证不爆精度。

总时间复杂度 \(O(S)\)

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
const int iinf=2147483647;
const int N=1e5+10;
char s1[N],s2[N];
int a[N],b[N],ar,br,ans=iinf,cnt1,cnt2,l1,l2;
void geta(int x)
{
	if(a[x]>1)a[x-1]+=a[x]>>1,a[x]&=1;
	if(a[x]<0)a[x-1]-=(1-a[x])>>1,a[x]&=1;
}
void getb(int x)
{
	if(b[x]>1)b[x-1]+=b[x]>>1,b[x]&=1;
	if(b[x]<0)b[x-1]-=(1-b[x])>>1,b[x]&=1;
}
signed main()
{
	a[0]=b[0]=1;
	cin>>s1>>s2;
	l1=strlen(s1);
	l2=strlen(s2);
	for(int i=0;i<l1;i++)
	{
		if(s1[i]=='1')ar++,a[ar]=0;
		if(s1[i]=='2')ar++,a[ar]=1;
		if(s1[i]=='U')geta(ar),ar--;
		if(s1[i]=='L')a[ar]--;
		if(s1[i]=='R')a[ar]++;
	}
	for(int i=ar;i>=0;i--)geta(i);
	for(int i=0;i<l2;i++)
	{
		if(s2[i]=='1')br++,b[br]=0;
		if(s2[i]=='2')br++,b[br]=1;
		if(s2[i]=='U')getb(br),br--;
		if(s2[i]=='L')b[br]--;
		if(s2[i]=='R')b[br]++;
	}
	for(int i=br;i>=0;i--)getb(i);
	cnt1=ar-min(ar,br)+br-min(ar,br);//对齐
	ar=br=min(ar,br);
	for(int i=0;i<=ar;i++)
	{
		cnt2=cnt2*2+a[i]-b[i];
		if(abs(cnt2)>ans)break;//剪枝
		ans=min(ans,abs(cnt2)+(ar-i)*2);
	}
	cout<<ans+cnt1;
}