Checkers
题目大意
这题貌似是多组输入,
先给你山歌数组 a b c 然后再给你三个数字x y z,问你a b c是否能通过转换变成x y z,如果不能就是输出 NO,否则输出YES然后输出最少的操作次数,
每次的操作规则为: 将一个数字移动到另一个关于另一个数字对称的位置,但是移动的这段距离上只存在这连个数字;比如说 1 2 3,这三个数字只能 变成 0 1 3或者1 3 4 (也就是选择跳2) 不能移动1或者3,因为这样移动的话,从起点到对称位置的数字是三个;
解题思路
这样我们可以发现一个规律,如果有一个数字y-x等于z-y,那么它只能移动y这个数字,并且任何一组数据都能变成y-x等于z-y,这个状态;
如果y-x>z-y那么就让y和z不断的向x逼近,如果y-x<z-y那就让x和y不断的向z逼近,最后肯定能达到这种平衡状态的 因为两个数的最小公倍数就是这样求得,这个和那个相同的道理,既然我们发现这个规律,那么我们就可以发现如果所给的两组数据的平衡状态都不一样,那么这肯定是NO,但是如果相同的话呢?,这时候我们可以想象一下,这两组数据相当于通过一个平衡变化过来的,那不是和树很像了?只有一个祖先,任何节点都可以走到祖先这个位置,让求的最少操作次数,不就是树上的两点的最小距离,并且这一题权值还都是1,我们可以用LCA呀。先计算出这两种状态到根节点的距离相加然后减去二倍的最近公共祖先到根节点的距离就是答案了。但是这一题的数据是在是太大了,如果直接构造一棵数,然后跑lca很有可能会TLE,那么我们怎么做呢?我们可以用二分去写呀,二分每个状态向上走x步的是否相同,如果相同就让x再小点,**还是有种倍增的感觉**大致的思路都有了。我们看下代码吧
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a[3];
ll b[3];
//求某个状态到达其平衡位置需要移动的次数
ll deep(ll x,ll y,ll z)
{
ll l=y-x;ll r=z-y;
if(l==r) return 0;
else if(l<r)//如果右边大于左边,就向右逼近
{
ll k=r/l;//这里看不懂的话模拟一遍就知道了
if(r%l==0) return k-1;//如果能整除,移动k-1后肯定是平衡位置
else return k+deep(x+k*l,y+k*l,z);//否则就先移动这么多次看看移动后啥情况
}
else{//这里是左边大于右边的情况
ll k=l/r;
if(l%r==0) return k-1;
else return k+deep(x,y-k*r,z-k*r);//需要向左迭代,所以这里是减
}
}
//找到移动 dep 次后的转态是什么
// & 这个运算符可以改变 x y z 的值,稍微方便点
void up(ll dep,ll &x,ll &y,ll &z)
{
ll l=y-x;
ll r=z-y;
if(l==r||dep==0) return ;//如果已经是平衡位置或者dep等0了就返回
else if(l<r)//右边大于左边,向右迭代
{
ll k=r/l;
ll tem;
if(r%l==0) tem=min(dep,k-1);//这里和上面的一样就是需要考虑dep是否够用
else tem=min(dep,k); //这里的 min函数很巧妙的别开了过多的分类讨论;
up(dep-tem,x+=tem*l,y+=tem*l,z);
}
else if(r<l){//和更新右边一样
ll k=l/r;
ll tem;
if(l%r==0) tem=min(dep,k-1);
else tem=min(dep,k);
up(dep-tem,x,y-=tem*r,z-=tem*r);
}
}
int main()
{
ios::sync_with_stdio(0);
while(cin>>a[0]) //多组输入
{
for(int i=1;i<3;i++) cin>>a[i];
for(int i=0;i<3;i++) cin>>b[i];
sort(a,a+3);sort(b,b+3);//先排下序,方便计算之前的距离
ll x=a[0],y=a[1],z=a[2];
ll x1=b[0],y1=b[1],z1=b[2];
//计算出到平衡位置的距离
ll len1=deep(x,y,z);
ll len2=deep(x1,y1,z1);
//让着两组数据向上各走几步,分别到达自己的平衡位置
up(len1,a[0],a[1],a[2]);
up(len2,b[0],b[1],b[2]);
//如果平衡位置不相同那么就不可能了,直接输出NO;
if(a[0]!=b[0]||a[1]!=b[1]||a[2]!=b[2])
{
cout<<"NO\n";
continue;
}
ll dis_0=len1+len2;//记录分别到根节点的距离之和
//这里和倍增lca一样,先将两者移动到同一深度
if(len1<=len2) up(len2-len1,x1,y1,z1);
else up(len1-len2,x,y,z);
len1=min(len1,len2);//等于这两个状态目前处于的深度
ll l=0,r=len1,mid;
ll dis_1=0;//记录这两个状态的最小公共祖先到达根节点的距离
while(l<=r)
{
ll x2=x,y2=y,z2=z;
ll x3=x1,y3=y1,z3=z1;
mid=(l+r)>>1;
up(len1-mid,x2,y2,z2);//看看他们到达相应位置后的转态如何
up(len1-mid,x3,y3,z3);
if(x3==x2&&y3==y2&&z3==z2)//如果相同说明这还不是最进的公共祖先
{
l=mid+1;//将lca到根节点的距离增大
dis_1=mid;
}
else r=mid-1;//否则就是太远了,应该拉近
}
cout<<"YES\n";
cout<<dis_0-dis_1*2<<"\n";//出入答案
}
return 0;
}