【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;
}
上一篇:最短路迪杰斯特拉算法


下一篇:方格取数加强版