【BZOJ2144】跳跳棋-二分+LCA

测试地址: 跳跳棋
做法: 本题需要用到二分+LCA。
一道神题。注意到三个棋子可以进行以下的跳跃:
1.中间的棋子跳过两边的棋子向外跳跃;
2.距离中间棋子较近的那个棋子跳过中间的妻子向内跳跃。
因为限制了只能跳过一个棋子,所以上面的两种跳跃方式就是全部了。而我们发现,除非两边的棋子和中间的棋子距离相等,通过第二种跳跃都只能到达一种状态,而第一种跳跃和第二种跳跃是互逆的操作,于是我们惊奇地发现:第一种跳跃可以看成从一点走到两个儿子,第二种跳跃可以看成跳到父亲,那么所有的状态就会形成一些二叉树。于是问题就变成了求两个状态的LCA,以及它们到那个LCA的步数之和。
首先,我们应该判断两个状态可不可以互达。要做到这一点,实际上就是看两个状态所在树的根是不是相同就行了。怎么样算出这个根呢?我们发现,令 b − a = d 1 , c − b = d 2 b-a=d_1,c-b=d_2 ba=d1,cb=d2,不妨设 d 1 &lt; d 2 d_1&lt;d_2 d1<d2(否则对称处理即可),那么 a , b a,b a,b两点可以一直向右,每次移动 d 1 d_1 d1的距离,直到 d 2 − k ⋅ d 1 ≤ d 1 d_2-k\cdot d_1\le d_1 d2kd1d1为止。于是我们发现,这几乎就是一个取模运算,除了当 d 1 ∣ d 2 d_1 | d_2 d1d2时,最后要剩下 d 1 d_1 d1的距离,移动 d 2 d 1 − 1 \frac{d2}{d1}-1 d1d21步,否则就剩下 d 2 % d 1 d_2\% d_1 d2%d1的距离,移动 ⌊ d 2 d 1 ⌋ \lfloor \frac{d2}{d1}\rfloor d1d2步。而根据与其类似的辗转相除的过程的复杂度,这样计算的复杂度是 O ( log ⁡ d ) O(\log d) O(logd)的,这样就能很快算出根了。
接下来就是求LCA的问题。回顾往常求LCA的思路,我们先把深度较深的点提升到和另一个点深度齐平,然后一起在树上跳。但在这里由于状态数很多,不可能用倍增处理,所以我们直接二分LCA的深度即可,对于每个 m i d mid mid求出两点深度在 m i d mid mid的祖先,复杂度和上面的算法一样是 O ( log ⁡ d ) O(\log d) O(logd)的。这样我们就得到了一个复杂度为 O ( log ⁡ 2 d ) O(\log^2 d) O(log2d)的算法,可以轻易地通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
ll a,b,c,x,y,z,f;
ll finala,finalb,finalc,finalx,finaly,finalz;
ll dis0,dis1;

void find(ll a,ll b,ll c,ll limit,ll &finala,ll &finalb,ll &finalc,ll &dis)
{
	if (b-a==c-b) return;
	if (b-a<c-b)
	{
		ll nxt=min((c-b)/(b-a)-((c-b)%(b-a)==0),limit-dis);
		dis+=nxt;
		nxt=nxt*(b-a);
		a+=nxt,b+=nxt;
	}
	else
	{
		ll nxt=min((b-a)/(c-b)-((b-a)%(c-b)==0),limit-dis);
		dis+=nxt;
		nxt=nxt*(c-b);
		b-=nxt,c-=nxt;
	}
	finala=a,finalb=b,finalc=c;
	if (dis==limit) return;
	find(a,b,c,limit,finala,finalb,finalc,dis);
}

int main()
{
	scanf("%lld%lld%lld",&a,&b,&c);
	if (a>b) swap(a,b);
	if (a>c) swap(a,c);
	if (b>c) swap(b,c);
	scanf("%lld%lld%lld",&x,&y,&z);
	if (x>y) swap(x,y);
	if (x>z) swap(x,z);
	if (y>z) swap(y,z);
	
	finala=a,finalb=b,finalc=c; 
	find(a,b,c,inf,finala,finalb,finalc,dis0);
	finalx=x,finaly=y,finalz=z;
	find(x,y,z,inf,finalx,finaly,finalz,dis1);
	if (finala!=finalx||finalb!=finaly||finalc!=finalz)
	{
		printf("NO");
		return 0;
	}
	
	if (dis0<dis1)
	{
		swap(dis0,dis1);
		swap(a,x);
		swap(b,y);
		swap(c,z);
	}
	find(a,b,c,dis0-dis1,finala,finalb,finalc,f=0);
	a=finala,b=finalb,c=finalc;
	ll l=0,r=dis1;
	while(l<r)
	{
		ll mid=(l+r)>>1;
		find(a,b,c,mid,finala,finalb,finalc,f=0);
		find(x,y,z,mid,finalx,finaly,finalz,f=0);
		if (finala==finalx&&finalb==finaly&&finalc==finalz) r=mid;
		else l=mid+1;
	}
	printf("YES\n%lld",dis0-dis1+(l<<1));
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值