1. 程式人生 > 實用技巧 >【Luogu P1852】 跳跳棋

【Luogu P1852】 跳跳棋

題目大意:

三個棋子在 \(a,b,c\) 位置,通過題目給出的跳動規則,問能否跳到 \(x,y,z\),如果能,最少多少步?

正文:

假設三個棋子分別在 \(a,b,c\quad(a<b<c)\),跳動規則其實就三個:

  1. \(b\)\(a\) 跳。

  2. \(b\)\(c\) 跳。

  3. \(a,c\)\(b\) 近的向內跳。

而第 1,2 個規則都會擴大範圍,只有第 3 個規則會縮小範圍,@ButterflyDew (uid=63727) 在題解中提到 對縮小邊界的跳法具有唯一性,也就是說將初始位置和終點位置都用第 3 規則跳,若最後產生交集(即相等)說明能夠到達。

把三個數看作一個三元數 \((a,b,c)\) 作為一棵樹的一個節點的狀態,也就是說問題就是樹上兩個不同節點的距離,像 LCA 一樣,先讓兩個節點跳到一個相同的深度,再二分距離,讓這兩個節點同時向上跳,最後就能出答案。

程式碼:

int jump(int a, int b, int c)
{
	int d1 = b - a, d2 = c - b, cnt = 0;
	if (d1 < d2)
	{
		int d = d2 % d1;
		cnt = d2 / d1;
		if (!d)
		{
			cnt --;
			d += d1;
		}
		cnt += jump (c - d - d1, c - d, c);
	} else
	if (d1 > d2)
	{
		int d = d1 % d2;
		cnt = d1 / d2;
		if (!d)
		{
			cnt --;
			d += d2;
		}
		cnt += jump (a, a + d, a + d + d2);
	} else
		arr[0] = a, arr[1] = b, arr[2] = c;
	return cnt;
}
void go(int a, int b, int c, int step)
{
	if(!step)
	{
		arr[0] = a, arr[1] = b, arr[2] = c;
		return;
	}
	int d1 = b - a, d2 = c - b, cnt = 0;
	if (d1 < d2)
	{
		int d = d2 % d1;
		cnt = d2 / d1;
		if (!d)
		{
			cnt --;
			d += d1;
		}
		if(step >= cnt)
			go (c - d - d1, c - d, c, step - cnt);
		else
			go (c - d - d1 * (cnt - step + 1), c - d - d1 * (cnt - step), c, 0);
	} else
	if (d1 > d2)
	{
		int d = d1 % d2;
		cnt = d1 / d2;
		if (!d)
		{
			cnt --;
			d += d2;
		}
		if(step >= cnt)
			go (a, a + d, a + d + d2, step - cnt);
		else
			go (a, a + d + d2 * (cnt - step), a + d + d2 * (cnt - step + 1), 0);
	} else
		arr[0] = a, arr[1] = b, arr[2] = c;
}

bool check(int mid)
{
	go(beg[0], beg[1], beg[2], mid);
	arr1[0] = arr[0], arr1[1] = arr[1], arr1[2] = arr[2];
	go(end[0], end[1], end[2], mid);
	if (arr1[0] != arr[0] && arr1[1] != arr[1] && arr1[2] != arr[2])
		return 0;
	return 1;
}

int main()
{
	scanf ("%d%d%d%d%d%d", &beg[0], &beg[1], &beg[2], &end[0], &end[1], &end[2]);	sort (beg, beg + 3);sort (end, end + 3);
	int step1 = jump(beg[0], beg[1], beg[2]);
	arr1[0] = arr[0], arr1[1] = arr[1], arr1[2] = arr[2];
	int step2 = jump(end[0], end[1], end[2]);
	if (arr1[0] != arr[0] && arr1[1] != arr[1] && arr1[2] != arr[2])
	{
		puts("NO");
		return 0;
	}
	if (step1 < step2)
	{
		ans = step2 - step1;
		go(end[0], end[1], end[2], step2 - step1);
		end[0] = arr[0], end[1] = arr[1], end[2] = arr[2];
	}
	if (step1 > step2)
	{
		ans = step1 - step2;
		go(beg[0], beg[1], beg[2], step1 - step2);
		beg[0] = arr[0], beg[1] = arr[1], beg[2] = arr[2];
	}
	int l, r = min(step1, step2);
	while (l < r)
	{
		int mid = (l + r) >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	printf("YES\n%d\n", (l * 2 + ans));
	return 0;
}