赛时(模拟赛)乱加优化写挂了,爬来写题解。
发现点的深度和路径长度都非常大,而且一个点有多种方式到达,考虑先用统一的方式存储两个点的位置,再进行求解。
存储
为了更好地表示当前的位置,考虑对每个点编号。首先想到类似线段树的编号方法:初始点编号为 \(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
可能使得上次操作的 L
或 R
失效,所以先执行 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;
}