[国家集训队]跳跳棋

题目大意:

给定一条数轴,上面有三个棋子,分别在\(a\) , \(b\), \(c\) 三个位置。

棋子只能在整点上,每个点只能最多只能有一个棋子。

现在,我们要把棋子用最少的步数跳到 \(x\), \(y\), \(z\) (棋子是没有区别的)。

跳动的规则很简单,任选一个棋子,选择另一个棋子为中轴棋子翻一个跟斗,跳了之后两棋子距离不变。一次只允许跳过一个棋子。

判断是否可以完成任务,如果可以,输出最少需要跳动的次数。

输入的所有值的绝对值不超过\(10^9\)

样例:

1 2 3
0 3 5
YES
2

思路:

%%%%%%%%%%

神题!神题!神题!

题目很简洁,正解很神奇!!!!

首先,假设一个递增三元组 \((x, y, z)\) ,来看一下这个三元组一共有多少种跳法。

\(y\) 向左跳过 \(x\) ,形成 \((2 \times x - y, x, z)\)

\(y\) 向右跳过 \(x\), 形成 \((x,z,2 \times z - y)\)

还有 \(x\)\(z\) 向内跳,但是因为只能越过一个棋子,所以最多只有一种跳法。

\(d_1 = y - x, d_2 = z - y\)

\(d_1 < d_2\), \((y, y + d_1, z)\)

\(d_1 > d_2\), \((x, z - d2, z)\)

\(d_1 = d_2\), 则该状态不可向内跳。

发现没有,若向内跳,必定只会有一种方法,且一定有边界。从边界转移到一个状态有且仅会有一种方法,其次一个状态往其他状态转移时有两种方法,且边界在不断扩大,所以不会有环。

上述描述是不是很像一颗二叉树???

边界就是根,两种向外跳的方法就是两个儿子,向内跳的方法就是父亲。

所以判断有没有解,就可以直接判断两颗树的根是否相同。

其实能想到现在,正解已经很近了,现在就是如何优化这个跳的过程。

回到上面向内跳的过程:

\((y, y +d_1, z)\)\((x,z - d_2, y)\)

若我往一边跳,那么这一边的 \(d\) 值是不变的。所以不用一次跳一下,一次不停地往那边跳,知道大于等于另一边的 \(d\)值。这个就是 \((d_1, d_2) \Rightarrow (d_2 \% d_1, d_1)\) ,这不就是一个\(gcd\) 的复杂度吗。

其次,统计答案就非常简单了,二分+LCA计算就好了。

代码:

#include <bits/stdc++.h>

using namespace std;

#define REP(i, a, n) for (register int i = a, _n = n; i <= _n; ++i)
#define DREP(i, a, n) for (register int i = a, _n = n; i >= _n; --i)
#define FOR(i, a, n) for (register int i = a, _n = n; i < _n; ++i)
#define EREP(i, a) for (register int i = first[a]; i; i = edge[i].nxt)
#define debug(x) cout << #x << " = " << x << endl

char IO;
inline int rd () {
	int res = 0;
	while ((IO = getchar()) && (IO < '0' || IO > '9'));
	while (IO >= '0' && IO <= '9') res = (res << 1) + (res << 3) + (IO ^ 48), IO = getchar();
	return res;
}

void jump (int& a, int& b, int& c, int& len) {
	int d1 = b - a, d2 = c - b;
	while (d1 != d2) {
		if (d1 < d2) {
			len += (d2 - 1) / d1;
			d2 = (d2 - 1) % d1 + 1;
			a = c - d1 - d2;
			b = c - d2;
		} else {
			len += (d1 - 1) / d2;
			d1 = (d1 - 1) % d2 + 1;
			b = a + d1;
			c = a + d1 + d2;
		}
		d1 = b - a, d2 = c - b;
	}
}
void Up (int& ta, int& tb, int& tc, int len) {
	int a = ta, b = tb, c = tc;
	while (len) {
		int d1 = b - a, d2 = c - b;
		if (d1 < d2) {
			int tim = min(d2 / d1, len);
			len -= tim;
			a = b + (tim - 1) * d1;
			b = b + tim * d1;
		} else {
			int tim = min(d1 / d2, len);
			len -= tim;
			c = b - (tim - 1) * d2;
			b = b - tim * d2;
		}
	}
	ta = a, tb = b, tc = c;
}

int a, b, c, l1, ta, tb, tc;
int x, y, z, l2, tx, ty, tz;

int main () {
	scanf ("%d%d%d", &a, &b, &c);
	scanf ("%d%d%d", &x, &y, &z);
	if (a > b) swap(a, b);
	if (b > c) swap(b, c);
	if (a > b) swap(a, b);
	if (x > y) swap(x, y);
	if (y > z) swap(y, z);
	if (x > y) swap(x, y);
	ta = a, tb = b, tc = c;
	tx = x, ty = y, tz = z;
	jump(ta, tb, tc, l1);
	jump(tx, ty, tz, l2);

	if (ta != tx || tb != ty || tc != tz) {
		puts("NO");
		return 0;
	}

	if (l1 > l2) {
		Up(a, b, c, l1 - l2);
	} else if (l1 < l2) {
		Up(x, y, z, l2 - l1);
	}

	if (a == x && b == y && c == z) return !printf ("YES\n%d\n", abs(l1 - l2));

	int l = 1, r = min(l1, l2), res;

    while (l <= r) {
    	int mid = l + r >> 1;
    	ta = a, tb = b, tc = c;
    	tx = x, ty = y, tz = z;
    	Up(ta, tb, tc, mid);
    	Up(tx, ty, tz, mid);
    	if (ta == tx && tb == ty && tc == tz) {
    		res = mid;
    		r = mid - 1;
    	} else {
    		l = mid + 1;
    	}
    }

	printf ("YES\n%d\n", res * 2 + abs(l1 - l2));

	return 0;
}

总结:

首先这题的确非常好,没有过多的算法,建模过程很有意思,也能顺水推舟的写下来。以后做题时可以手玩一下样例,发现一些性质,而不是瞪着题目发呆。

posted @ 2019-07-12 21:38  ZPAYAUR  阅读(424)  评论(1编辑  收藏  举报