hdu1722 bjfu1258 辗转相除法
这题就是个公式,代码极简单。但我想,真正明白这题原理的人并不多。很多人只是随便网上一搜,找到公式a了就行,其实这样对自己几乎没有提高。
鉴于网上关于这题的解题报告中几乎没有讲解原理的,我就多说几句,也不是严格的证明,给大家分享一下。
题目是说有p人或q人吃蛋糕,需要提前把蛋糕切好而能同时满足这两种情况,使蛋糕的块数最少。为了方便表述,不妨设p < q
首先,记p和q的最小公倍数为m,则把蛋糕平均切成m块,一定是能满足条件的,但这不是最优解,暂记为解法①。
我们的工作就是把解法①的这m块中的一些尽可能合并起来(也就是之前不切开),使块数尽可能少。那么怎么合并呢?
第一个能肯定的是,每一块最大为1/q,否则当来的人数是q时就不行了。而最小呢,就是1/m。所以解法②可以这样:先切出p份1/q大小的块,剩下的部分全切成1/m大小的,这样显然也是能满足要求的。
比如p = 2, q = 3,那么就切成1/3, 1/3, 1/6, 1/6。这个例子这恰好是最优解,但并不总是所有情况都这样。
比如p = 6, q = 10,最优应该切成1/10, 1/10, 1/10, 1/10, 1/10, 1/10, 1/15, 1/15, 1/15, 1/15, 1/30, 1/30, 1/30, 1/30
而p = 24, q = 60的最优解应该是1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/60, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120, 1/120
聪明的人应该已经看出来了,其实就是继续把后面的1/m的小块合并,这些小块越来越小,最后最小的都是1/m大小的。而中间大小的块的数量怎么确定呢?
比如p = 6, q = 10时,1/15块的数量应该是4而不是6,因为如果数量是6,仅仅能满足当人数为6的情况(这时每人吃一块1/10的,加一块1/15的),但当人数为10时,这些1/15的小块无法组成1/10的大块。所以必须预留一些更小的块出来。这时,1/15的块只要保留(q-p)=4块即可。剩下的情况就是递归处理,也就是说,当切出6个1/10的块后,接下来的问题等价于处理p = 10 - 6 = 4, q = 6的情况,只是这时剩下的蛋糕只是原来的4/10而已。
如果p < q/2时成立,比如当p = 24, q = 60时,切出24个1/60的块后,应该继续切出24个1/60的块,然后再考虑1/120的小块。
这个刚好就是辗转相除法求最大公约数的过程。具体的原理大家再细细品味。
下面是代码:
/* * Author : ben */ #include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <ctime> #include <iostream> #include <algorithm> #include <queue> #include <set> #include <map> #include <stack> #include <string> #include <vector> #include <deque> #include <list> #include <functional> #include <numeric> #include <cctype> using namespace std; int gcd(int a, int b) { int r; while (b) { r = a % b; a = b; b = r; } return a; } int main() { int p, q; while (scanf("%d%d", &p, &q) == 2) { printf("%d\n", p + q - gcd(p, q)); } return 0; }