bzoj2144 [2011集训队出题] 跳跳棋 倍增 lca

24 篇文章 0 订阅
17 篇文章 0 订阅

Description


跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。
  我们用跳跳棋来做一个简单的游戏:棋盘上有3颗棋子,分别在a,b,c这三个位置。我们要通过最少的跳动把他们的位置移动成x,y,z。(棋子是没有区别的)
  跳动的规则很简单,任意选一颗棋子,对一颗中轴棋子跳动。跳动后两颗棋子距离不变。一次只允许跳过1颗棋子。

在这里插入图片描述

写一个程序,首先判断是否可以完成任务。如果可以,输出最少需要的跳动次数。

Data Constraint


20% 输入整数的绝对值均不超过10
40% 输入整数的绝对值均不超过10000
100% 绝对值不超过10^9

Solution


很神奇的一道题

首先给123棋子排序,设中间棋子为p,前1、2棋子距离为d1,2、3棋子为d2,那么分类如下:
d1 < d2,根据题意可以把棋子1跳到棋子2、3中间位置
d1 = d2,我们不能把两边的棋子再往中间跳了
d1 > d2,类似地可以把棋子3跳到棋子1、2中间位置

把三种情况都视为二叉树上节点,d1=d2作为一棵二叉树的根节点,那么题意转为求树上两点最短路,进而想到lca
对于无解的情况把一个状态暴力向上跳INF找到根,比较两个根即可
对于100%的数据可以把辗转相减用相除加速,这样复杂度就是log级别的了

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)
#define min(x,y) ((x)<(y)?(x):(y))
const int INF=0x7777777f;
struct pos{
	int p,d1,d2;
	bool operator ==(pos b) {return b.p==p&&b.d1==d1&&b.d2==d2;}
}a,b;
int ta[3],tb[3];
int get_dep(pos now) {
	if (now.d1==now.d2) return 0;
	if (now.d1<now.d2) return (now.d2-1)/now.d1+get_dep((pos){now.p+now.d2-(now.d2-1)%now.d1,now.d1,(now.d2-1)%now.d1+1});
	else return (now.d1-1)/now.d2+get_dep((pos){now.p-now.d1+(now.d1-1)%now.d2,(now.d1-1)%now.d2+1,now.d2});
}
void up(pos &now,int k) {
	while (k) {
		if (now.d1==now.d2) return;
		if (now.d1<now.d2) {
			int tmp=min(k,(now.d2-1)/now.d1);
			k-=tmp;
			now=(pos){now.p+tmp*now.d1,now.d1,now.d2-tmp*now.d1};
		} else {
			int tmp=min(k,(now.d1-1)/now.d2);
			k-=tmp;
			now=(pos){now.p-tmp*now.d2,now.d1-tmp*now.d2,now.d2};
		}
	}
}
int main(void) {
	rep(i,0,2) scanf("%d",&ta[i]);
	std:: sort(ta,ta+3); a=(pos){ta[1],ta[1]-ta[0],ta[2]-ta[1]};
	rep(i,0,2) scanf("%d",&tb[i]);
	std:: sort(tb,tb+3); b=(pos){tb[1],tb[1]-tb[0],tb[2]-tb[1]};
	pos root_a=a; up(root_a,INF);
	pos root_b=b; up(root_b,INF);
	if (!(root_a==root_b)) {
		puts("NO");
		return 0;
	}
	int dep_a=get_dep(a);
	int dep_b=get_dep(b);
	int ans=dep_a+dep_b;
	if (dep_a<dep_b) {std:: swap(dep_a,dep_b); std:: swap(a,b);}
	up(a,dep_a-dep_b); dep_a=dep_b;
	drp(i,30,0) {
		int k=1<<i;
		if (k<=dep_a) {
			pos tmp_a=a;
			pos tmp_b=b;
			up(tmp_a,k); up(tmp_b,k);
			if (!(tmp_a==tmp_b)) {
				a=tmp_a;
				b=tmp_b;
				dep_a-=k;
			}
		}
	}
	if (!(a==b)) {up(a,1); up(b,1);}
	printf("YES\n%d\n",ans-2*get_dep(a));
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值