ZOJ_3593_One person game_扩展欧几里德

累得一笔


题意:

一个人站在数轴的A点,目的地在B点,他可以每次向左或向右走a, b, a+b距离,问他最少多少步走到目的地。



Input

There are multiple test cases. The first line of input is an integer T(0 < T ≤ 1000) indicates the number of test cases. Then T test cases follow. Each test case is represented by a line containing four integers 4 integers A, B, a and b, separated by spaces. (-231A, B < 231, 0 < a, b < 231)

Output

For each test case, output the minimum number of steps. If it's impossible to reach point B, output "-1" instead.


这题真是醉得厉害,比赛的时候老早就想出了正确算法,结果被一直爆long long WA。

先用欧基里德解方程 a x + b y = A - B 搞特解,然后搜索满足等式的x, y使得步数最少。

对于特定的a, b数量,证明不可能对于a或b步长(a+b过程中认为是包含一个a步长和一个b步长),同时存在向右和向左两种情况的走法步数最短。以a为例反证法证明,假设最优走法同时存在向右走a和向左走a,如果不存在b步长,那么向左向右两次走没有发生实际位移却增加了两次步数,无必要;如果存在至少一个b步长,不妨设其中一个b步长向右,如果不走一次a+b,那么和上一种情况一样,两个方向的a无必要,那么一次向右走(a+b),一次向左走a,造成同样位移的走法向右走b,只走了一步,因此最优解不同时存在a, b中任意一个步长向两个方向。

根据上面的推理,对于a x + b y = A - B,这个方程的任意一个解x, y,我们都可以简单理解为向右走了x步a步长,y步b步长,能做的优化是当x, y同号是将其结合为一个a + b从而减少一此迈步。

那么最优的方案也就出来了,使x, y尽量接近。如x, y同号,步数为其中更大的那一个。如异号,步数为x, y绝对值之和。这两种情况都是在x, y最接近的两种方案(x大和y大,如x, y可以相等则只有一种)中产生最优。

实现上,特解套扩展欧几里德算法,之后就是搜索最优解,我用的是二分搜索,结果题目数据范围太大,估算出错,导致二分搜索过程中爆了long long。。。

后来看到网上的题解,里面的做法和我的基本一样,除了比我机智地用O(1)的办法算出了最优解,以及多算了两种没有必要考虑的情况,其他都一样。。。O(1)找最优的办法是用x, y特解中大的减小的得到一个距离,然后每次更改x, y都有一个改变的最小值,用两者相除即可。


代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<fstream>
using namespace std;
#define inf (1LL<<60)
long long a,b,c;
long long A,B;
long long max(long long a,long long b){
	return a>b ? a : b;
}
void ex_gcd(long long a,long long b,long long& c,long long& x,long long& y){
	if(!b){
		c=a;
		x=1;
		y=0;
	}
	else{
		ex_gcd(b,a%b,c,y,x);
		y-=x*(a/b);
	}
}
long long calc_ans(long long x,long long y){
	if(x>=0&&y>=0)	return max(x,y);
	else if(x<=0&&y<=0)	return -min(x,y);
	else if(x<0)	return y-x;
	else	return x-y;
}
long long bin(long long bgr,long long sml,long long b_ch,long long s_ch){
	/*long long l=0,r=(bgr-sml)/s_ch+1;
	while(l+1<r){
		long long m=(l+r)>>1;
		if(sml+m*s_ch<=bgr-m*b_ch)	l=m;
		else	r=m;
	}
	*/
	long long l=(bgr-sml)/(b_ch+s_ch);
	long long r=l+1;
	long long ret = min(calc_ans(sml+l*s_ch,bgr-l*b_ch),calc_ans(sml+r*s_ch,bgr-r*b_ch));
	return ret;
}
int main(){
	int cs;
	scanf("%d",&cs);
	while(cs--){
		scanf("%lld%lld%lld%lld",&A,&B,&a,&b);
		if(A==B){	puts("0");	continue;}
		long long x,y;
		ex_gcd(a,b,c,x,y);
		x*=(B-A)/c;
		y*=(B-A)/c;
		long long ans;
		if(x<y)	ans=bin(y,x,a/c,b/c);
		else	ans=bin(x,y,b/c,a/c);
		printf("%lld\n",ans);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值