[ABC193E] Oversleeping

前言

扩展欧几里得?几万年前学的东西,幸好最后写出来了,但还是值得复习一下(指水题解)

题目

AtCoder

题目大意:

\(T\) 组数据。

时间从 \(0\) 时刻开始,有一辆火车从 \(A\) 地出发,在 \(A\) 地与 \(B\) 地之间来回跑,单趟时间为 \(X\) 秒,每到一个地点停 \(Y\) 秒。

你初始在 \(B\) 地,先睡 \(P\) 秒,再醒着 \(Q\) 秒,一直循环。

询问你是否可以上车,即是否存在一个时刻你醒着,且车停在 \(B\) 地。如果可以,输出最小的上车时间。如果不能,输出 infinity

\(1\le T\le 10;1\le X,P\le 10^9;1\le Y,Q\le 500.\)

讲解

你看这个 \(Y,Q\) 这么小,一看就可以枚举,记为 \(y,q\)

那么这就可以转换为一个同余方程问题,我们设火车经历 \(i\) 个周期,人经历 \(j\) 个周期,可列出方程:

\[X+i(2X+2Y)+y = Pj+Q(j-1)+q \]

变形可得:

\[j(P+Q)-i(2X+2Y) = Q+X+y-q \]

\(a=P+Q,b=(2X+2Y),d=\gcd(a,b)\),于是用扩展欧几里得先求出特殊解,然后再找最小的时间即可。

值得注意的是,\(i,j\ge 0\),但此时我们需要解的方程其实是:\(aj-bi=kd\),但扩欧解的方程是 \(aj+bi=d\),所以我们解出来的 \(i\) 需要满足小于等于 \(0\)

找最小的时间其实就是找最大的 \(i\in(-\infty,0]\) 且最小的 \(j\in[0,+\infty)\),再根据他们算就行。

实现其实不难。

代码

LL exgcd(LL a,LL b,LL &x,LL &y)
{
	if(!b)
	{
		x = 1;
		y = 0;
		return a;
	}
	LL ret = exgcd(b,a%b,y,x);
	y -= x*(a/b);
	return ret;
}

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	for(int T_T = Read(); T_T ;-- T_T)
	{
		X = Read(); Y = Read(); //on the way,stopping
		P = Read(); Q = Read(); //asleep,awake
		LL ans = INF,J,I;
		LL d = exgcd(P+Q,2*(X+Y),J,I);
		for(int y = 0;y < Y;++ y) 
			for(int q = 0;q < Q;++ q)
			{
				LL deng = Q+X+y-q,i,j;
				if(deng % d) continue;
				j = J; i = I;
				LL t1 = (P+Q) / d,t2 = 2*(X+Y) / d; i *= (deng / d); j *= (deng / d);
				if(i > 0) 
				{
					LL woc = i / t1;
					i -= woc * t1;
					j += woc * t2; 
				}
				if(j < 0)
				{
					LL woc = (-j) / t2;
					j += woc * t2;
					i -= woc * t1;
				}
				while(i > 0 || j < 0) i = i - t1,j = j + t2;
				LL wrng = Min((-i) / t1,j / t2);//both too large
				i += wrng * t1;
				ans = Min(ans,X+y+2*(-i)*(X+Y));
			}
		if(ans == INF) printf("infinity\n");
		else Put(ans,'\n');
	}
	return 0;
}
/*
草稿纸
X+2iX+2iY+y = Pj+Q(j-1)+q
X+2iX+2iY+y = Pj+Qj-Q+q
2i(X+Y)-j(P+Q) = q-Q-X-y
j(P+Q)-i(2X+2Y) = Q+X+y-q

a(x+b)+b(y-a) = d
k(a(x+b)+b(y-a)) = kd
*/
posted @ 2021-05-24 22:21  皮皮刘  阅读(329)  评论(0编辑  收藏  举报