1. 程式人生 > 實用技巧 >[LintCode 1674.] 倒可樂

[LintCode 1674.] 倒可樂

LintCode 1674. 倒可樂

CAT 專屬題目 中等難度題

替代連結 HDU 1495.

題目描述

給定一個容積為 s 的裝滿可樂的瓶子和兩個容積分別為 n 和 m 的空杯子, 其中 n + m = s. 問能否通過在三個容器之間來回倒可樂來平分這 s 體積的可樂? 如果可以, 返回最少傾倒可樂的次數; 反之返回 -1.

由於瓶子和杯子上沒有刻度, 所以當你從一個容器倒可樂到另一個容器中時, 只能一直倒可樂直到一個容器空了或者另一個滿了為止.

樣例

樣例 1:

輸入: s = 4, n = 1, m = 3
輸出: 3
解釋:
首先, 從瓶子倒3體積的可樂到第二個杯子, 瓶子和兩個杯子中分別有 1, 0, 3 體積的可樂.
然後, 倒1體積的可樂從第二個杯子到第一個杯子, 此時三個容器分貝有 1, 1, 2 體積的可樂.
最後, 倒1體積的可樂從第一個杯子到瓶子, 此時瓶子和第二個杯子中都有 2 體積的可樂, 平分.
樣例 2:

輸入: s = 7, n = 4, m = 3
輸出: -1
解釋: 7 體積的可樂不能被平分.

注意事項

s <= 100

解題思路

兩種思路:

  1. 第一種方法是搜尋,使用BFS,三個杯子兩兩操作,每一步都有6種傾倒方式。這種也能AC,開銷大一點。
  2. 第二種是數論。找出其中的數學規律,加以解決。

我們注意到,s = m + n,從而可以得出結論 gcd(m, n) == gcd(s, m, n)

如果m == n,只需要倒一次就可以了。
如果m != n不妨假設m > n,那麼最後平分水的時候,一半在s中,一半在m中,因為n的體積不到瓶子的一半。
s、m、n 之間的倒水,其實就是輾轉相減,最後能減出的最小單位是 gcd(m, n)

設 m 和 n 各自淨倒水的次數分別為 x 和 y,即各自倒出水次數減去倒入水次數之差為 x 和 y,則兩個杯子倒水次數之和最少為 s / gcd(m,n)
下面的推導使用 a 和 b 表示的 m 和 n:

由於x 和 y 不可能同正同負,必然一個正一個負。

所有的操作其實都是 s->m m->n n->s 這三個操作的排列組合而已。
例如:

  1. (s,m,n)=(4,3,1) 的倒水方案是: s->m, m->n, n->s
  2. (s,m,m)=(6,5,1) 的倒水方案是: s->m, m->n, n->s, m->n, n->s
  3. (s,m,n)=(8,5,3) 的倒水方案是: s->m, m->n, n->s, m->n, s->m, m->n, n->s

可以看到,每一次兩個杯子之間的操作,都伴隨著一次大瓶子的輔助使用。
我們把倒水操作兩兩一組劃分,可以看到每個組要麼是 m/n 的淨倒出組,要麼是 m/n 的淨倒入組,沒有任何一組是隻有m和n都倒出倒入相等次數的。
並且最後一組只有半個,所以總步數是 (c + d) / 2 * 2 - 1 = s / gcd(m, n) - 1

參考程式碼

class Solution {
public:
    /**
     * @param s: the volume of cola
     * @param n: the volume of the first cup
     * @param m: the volume of the second cup
     * @return: the minimum number of times to be inverted
     */
    int getMinTimes(int s, int n, int m) {
        // Write your code here
        s /= __gcd(n,m);
        if (s % 2 == 0) return s-1;
        return -1;
    }
};

參考文獻:

https://www.cnblogs.com/ECJTUACM-873284962/p/6750320.html