题目大意:数轴上三个棋子,初始位置x,y,z。用最少操作次数变成a,b,c。每次操作选一个棋子,以另一棋子为中轴跳动,跳跃后距离不变,一次只能跳过一颗棋子
题解:观察发现,状态的转移形成了一张图,可以在图上跑最短路,正确性显然,但是状态过于复杂,难以建立模型。再观察一下(逃,发现若(x,y,z)往两边跳得到状态(u,v,w),那么(x,y,z)是(u,v,w)往中间跳得到状态,状态间有玄♂妙的关系,如果用向里跳作父结点,向外跳作儿子结点,就可以建成一颗树了。答案就变成了求树上两点的距离
对于一般的树上两点距离,一般采用lca计算,一般来说,需要快速得到结点的祖先。下面考虑如何快速得到状态(x,y,z)往中间跳k步得到的状态
令a=y−x,b=z−y,设a>b,(x,y,z)的父亲(u,v,w),满足v−u=a−b,w−v=b,定义状态(a,b)可以转移到状态(a−b,b),每一个状态(x,y,z)对应唯一一个(a,b)
如果a>b,那么(a,b)->(a-b,b)或(a,b-a),那么难道不可以用取模来加速运算吗?即(a,b)->(a%b,b)或(a,b%a),刚好和求gcd一样!因此这么做就变成O(log2b)了!
先判断初始状态和目标状态是否在一颗树中(即根节点是否相同),若不在输入NO,否则按照倍增求lca,先把初始状态和目标状态提到同一个深度,然后二分到lca的距离,时间复杂度 O(log2n)
Orz
我的收获:状态的表示,加速转移
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define INF 1000000000
int len,k;
int lena,lenb,mid,ans;
struct node{
int x[4];
void in(){for(int i=1;i<=3;i++) scanf("%d",&x[i]);sort(x+1,x+1+3);}
bool operator ==(const node &a)const
{return x[1]==a.x[1]&&x[2]==a.x[2]&&x[3]==a.x[3];}
}a,b,p,q;
node getfa(node t,int rst)
{
for(len=0;rst;len+=k){
int u=t.x[2]-t.x[1],v=t.x[3]-t.x[2];
if(u==v) return t;
if(u<v) k=min((v-1)/u,rst),t.x[1]+=k*u,t.x[2]+=k*u,rst-=k;
else k=min((u-1)/v,rst),t.x[2]-=k*v,t.x[3]-=k*v,rst-=k;
}
return t;
}
void work()
{
if(!(p==q)){puts("NO");return ;}
if(lena<lenb) swap(a,b),swap(lena,lenb);
a=getfa(a,lena-lenb);
for(int l=0,r=lenb;l<=r;){
//加上等于!!
mid=(l+r)>>1;
if(getfa(a,mid)==getfa(b,mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("YES\n%d\n",ans*2+lena-lenb);
}
void init()
{
a.in();b.in();
p=getfa(a,INF);lena=len;
q=getfa(b,INF);lenb=len;
}
int main()
{
init();
work();
return 0;
}