Sicily1099-Packing Passengers-拓展欧几里德算法

最终代码地址:https://github.com/laiy/Datastructure-Algorithm/blob/master/sicily/1099.c

做这题的时候查了别人的做法花了半天都没搞明白怎么做的,我认为别的博客写的难以让人理解所以就造了这个轮子。

题目:

1099. Packing Passengers

Constraints

Time Limit: 1 secs, Memory Limit: 32 MB

Description

PTA, Pack ‘em Tight Airlines is attempting the seemingly impossible—to fly with only full planes and still make a profit. Their strategy is simplicity and efficiency. Their fleet consists of 2 types of equipment (airline lingo for airplanes). Type A aircraft cost costA dollars to operate per flight and can carry passengersA passengers. Type B aircraft cost costB dollars to operate per flight and can carry passengersB passengers.

PTA has been using software that works well for fewer than 100 passengers, but will be far too slow for the number of passengers they expect to have with larger aircraft. PTA wants you to write a program that fills each aircraft to capacity (in keeping with the name Pack ‘em Tight) and also minimizes the total cost of operations for that route.

Input

The input file may contain data sets. Each data set begins with a line containing the integer n (1 <= n <= 2,000,000,000) which represents the number of passengers for that route. The second line contains costA and passengersA, and the third line contains costB and passengersB. There will be white space between the pairs of values on each line. Here, costA, passengersA, costB, and passengersB are all nonnegative integers having values less than 2,000,000,001.
After the end of the final data set, there is a line containing “0” (one zero) which should not be processed.

Output

For each data set in the input file, the output file should contain a single line formatted as follows:
Data set <N>: <A> aircraft A, <B> aircraft B
Where <N> is an integer number equal to 1 for the first data set, and incremented by one for each subsequent data set, <A> is the number of airplanes of type A in the optimal solution for the test case, and <B> is the number of airplanes of type B in the optimal solution. The ‘optimal‘ solution is a solution that lets PTA carry the number of passengers specified in the input for that data set using only airplanes loaded to their full capacity and that minimizes the cost of operating the required flights. If multiple alternatives exist fitting this description, select the one that uses most airplanes of type A. If no solution exists for PTA to fly the given number of passengers, the out line should be formatted as follows:
Data set <N>: cannot be flown

Sample Input

600
30 20
20 40
550
1 13
2 29
549
1 13
2 29
2000000000
1 2
3 7
599
11 20
22 40
0

Sample Output

Data set 1: 0 aircraft A, 15 aircraft B
Data set 2: 20 aircraft A, 10 aircraft B
Data set 3: 11 aircraft A, 14 aircraft B
Data set 4: 6 aircraft A, 285714284 aircraft B
Data set 5: cannot be flown

题意就是求出passenger_A * x + passenger_B * y = passengers, 使得cost_A * x + cost_B * y最小。

我起初的想法是算出A和B哪个性价比大,然后取性价比大的那个最大的可能,再逐步递减到能够整除为止,这样做的效率是0.05s。

代码如下:

 1 // Problem#: 1099
 2 // Submission#: 4376506
 3 // The source code is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License
 4 // URI: http://creativecommons.org/licenses/by-nc-sa/3.0/
 5 // All Copyright reserved by Informatic Lab of Sun Yat-sen University
 6 #include <cstdio>
 7
 8 inline int fix_upper(int upper, int passengers, int passenger_upper, int passenger_others) {
 9     int temp = passengers - upper * passenger_upper;
10     while (temp % passenger_others) {
11         if (upper == 0)
12             return -1;
13         upper--, temp += passenger_upper;
14     }
15     return upper;
16 }
17
18 int main() {
19     int passengers;
20     int cost_A, passenger_A, cost_B, passenger_B;
21     int upper, others;
22     int count = 1;
23     while (scanf("%d", &passengers) && passengers) {
24         scanf("%d %d", &cost_A, &passenger_A);
25         scanf("%d %d", &cost_B, &passenger_B);
26         if (passenger_A == 0 && passenger_B == 0) {
27             printf("Data set %d: cannot be flown\n", count++);
28             continue;
29         }
30         if ((passenger_A == 0 && passenger_B != 0) || (cost_A == 0 && cost_B == 0 && passenger_B > passenger_A)) {
31             if (passengers % passenger_B)
32                 printf("Data set %d: cannot be flown\n", count++);
33             else
34                 printf("Data set %d: %d aircraft A, %d aircraft B\n", count++, 0, passengers / passenger_B);
35             continue;
36         }
37         if ((passenger_B == 0 && passenger_A != 0) || (cost_A == 0 && cost_B == 0 && passenger_A > passenger_B)) {
38             if (passengers % passenger_A)
39                 printf("Data set %d: cannot be flown\n", count++);
40             else
41                 printf("Data set %d: %d aircraft A, %d aircraft B\n", count++, passengers / passenger_A, 0);
42             continue;
43         }
44         if (double(cost_A) / double(passenger_A) <= double(cost_B) / double(passenger_B)) {
45             upper = passengers / passenger_A;
46             upper = fix_upper(upper, passengers, passenger_A, passenger_B);
47             others = (passengers - upper * passenger_A) / passenger_B;
48             if (upper != -1)
49                 printf("Data set %d: %d aircraft A, %d aircraft B\n", count++, upper, others);
50             else
51                 printf("Data set %d: cannot be flown\n", count++);
52         } else {
53             upper = passengers / passenger_B;
54             upper = fix_upper(upper, passengers, passenger_B, passenger_A);
55             others = (passengers - upper * passenger_B) / passenger_A;
56             if (upper != -1)
57                 printf("Data set %d: %d aircraft A, %d aircraft B\n", count++, others, upper);
58             else
59                 printf("Data set %d: cannot be flown\n", count++);
60         }
61     }
62     return 0;
63 }                                 

写的很丑请见谅,我只是为了AC,我知道这样做并不好当时。

然后就来讲一下更好的做法:

我们要求的是x, y满足passenger_A * x + passenger_B * y = passengers。

拓展欧几里德算法其实就是在欧几里德算法求解过程中把x和y算出来了,具体是这样的:a * x + b * y = gcd(a, b)。

其实就是把上面等式的x和y求出来了,原理如下:

我们知道欧几里德算法原理是gcd(a, b) = gcd(b, a % b)不断递归下去,拓展欧几里德算法其实也是这个递归,只不过多加了一些x和y的赋值罢了。

假设在某一次递归过程中,a‘ = b, b‘ = a % b = a - a / b * b(C语言整数算法)。

那么gcd(a, b) = gcd(a‘, b‘) = a‘x + b‘y。

消去a‘, b‘得到:ay +b(x - a  /  b * y) = Gcd(a, b)。

可以看到,这里的系数a的系数为y,b的系数为x - a / b * y。

通过这个原理递归我们最终得到的x和y就满足a * x + b * y = gcd(a, b)。

好,但是从这个算法也看不到和我们题目的关系是不是?

来看,首先有:passenger_A * x + passenger_B * y = gcd(passenger_A, passenger_B)

我们把上面的式子同时乘于passengers / gcd(passenger_A, passenger_B)试试:

passenger_A * (x * passengers / gcd(passenger_A, passenger_B)) + passenger_B * (y * passengers / gcd(passenger_A, passenger_B)) = passengers。

这个式子是不是就和passenger_A * x + passenger_B * y = passengers吻合了?

对应的x为(x * passengers / gcd(passenger_A, passenger_B)), y为(y * passengers / gcd(passenger_A, passenger_B))。

所以:我们先用欧几里德算法求出passenger_A * x + passenger_B * y = gcd(passenger_A, passenger_B)的x, y。

然后在通过变换得到passenger_A * x + passenger_B * y = passenger的x, y。

现在设这里求得的x, y为x0, y0。

这样就得到了满足以上等式一组的x0和y0的解了。

然后来看线型同余方程:

在数论中,线性同余方程是最基本的同余方程,“线性”表示方程的未知数次数是一次,即形如:

的方程。此方程有解当且仅当 b 能够被 a 与 n 的最大公约数整除(记作 gcd(a,n) | b)。这时,如果 x0 是方程的一个解,那么所有的解可以表示为:

其中 d 是a 与 n 的最大公约数。在模 n 的完全剩余系 {0,1,…,n-1} 中,恰有 d 个解。

所以,如果passengers不能整除gcd(passenger_A, passenger_B)则式子是无解的,若有解:

x = x0 + (passenger_B / gcd(passenger_A, passenger_B)) * k, y = y0 - (passenger_A / gcd(passenger_A, passenger_B)) * k, k为任意整数。

然后,根据题意,x >= 0, y >= 0。

所以有:

x0 + (passenger_B / gcd(passenger_A, passenger_B)) * k >= 0

=>

k >= (-x0) / (passenger_B / gcd(passenger_A, passenger_B))

y0 - (passenger_A / gcd(passenger_A, passenger_B)) * k >= 0

=>

k <= (y0) / (passenger_A / gcd(passenger_A, passenger_B))。

所以k的范围为:[(-x0) / (passenger_B / gcd(passenger_A, passenger_B)), (y0) / (passenger_A / gcd(passenger_A, passenger_B))]

然后来看:

我们的目标是cost_A * x + cost_B * y最小,带入x和y的表达式得:

cost_A * (x0 + (passenger_B / gcd(passenger_A, passenger_B)) * k) + cost_B * (y0 - (passenger_A / gcd(passenger_A, passenger_B)) * k)

消去x0, y0, gcd无关因素影响, 有:

cost_A * passenger_B * k - cost_B * passenger_A * k = k * (cost_A * passenger_B - cost_B * passenger_A)

好,那么就很简单了,如果 (cost_A * passenger_B - cost_B * passenger_A)为负,则k取最大值即可。

如果为正,k取最小值即可。然后得到k的值带入求出对应x和y即可。

代码如下:

 1 #include <cstdio>
 2 #include <cmath>
 3
 4 inline long long gcd_extend(int &a, int b, long long *x, long long *y) {
 5     static long long r, t;
 6     if (b == 0) {
 7         *x = 1, *y = 0;
 8         return a;
 9     } else {
10         r = gcd_extend(b, a % b, x, y);
11         t = *x;
12         *x = *y;
13         *y = t - a / b * *y;
14         return r;
15     }
16 }
17
18 int main() {
19     int passengers;
20     int cost_A, passenger_A, cost_B, passenger_B;
21     int count = 1;
22     long long lower, upper, k, gcd, x, y;
23     while (scanf("%d", &passengers) && passengers) {
24         scanf("%d %d", &cost_A, &passenger_A);
25         scanf("%d %d", &cost_B, &passenger_B);
26         gcd = gcd_extend(passenger_A, passenger_B, &x, &y);
27         if (passengers % gcd == 0) {
28             x *= passengers / gcd;
29             y *= passengers / gcd;
30             upper = floor((double)y / (passenger_A / gcd));
31             lower = ceil((double)-x / (passenger_B / gcd));
32             k = passenger_B * cost_A - passenger_A * cost_B <= 0 ? upper : lower;
33             printf("Data set %d: %lld aircraft A, %lld aircraft B\n", count++, x + (passenger_B / gcd) * k, y - (passenger_A / gcd) * k);
34         } else
35             printf("Data set %d: cannot be flown\n", count++);
36     }
37     return 0;
38 }

这种做法效率为0.02s。

时间: 2024-11-07 03:29:48

Sicily1099-Packing Passengers-拓展欧几里德算法的相关文章

如何使用循环而不是递归反推的方式实现拓展欧几里德算法

平常我们使用拓展欧几里德算法求pm + qn = gcd(m, n)这种表示时,一般都会选择递归的方式来实现,因为欧几里得算法的递归深度最多也只有O(lgn), according to lame's theorem,所以这个递归用栈是可以忽略的. 但其实只需要循环就可以求出一组pm + qn = gcd(m, n)的表示,将栈深度保持在O(1),这样的写法在使用函数调用的高级语言中看起来复杂一点但在汇编编程时就显得比较简单. 方法是假设第k次迭代中的两个数分别为 M(k) 和 N(k),我们始

拓展欧几里德算法求逆元2

1 void gcd(int a,int b,int &d,int &x,int &y) 2 { 3 if(!b) 4 { 5 d=a; 6 x=1; 7 y=0; 8 } 9 else 10 { 11 gcd(b,a%b,y,x); 12 y-=x*(a/b); 13 } 14 } 15 16 //计算模n下a的逆元,如果不存在逆元,返回-1 17 int inv(int a,int n) 18 { 19 int d,x,y; 20 gcd(a,n,d,x,y); 21 retu

欧几里德算法(自写理解)

gcd欧几里德算法  求取最大公约数gcd(a,b) 这个不用多说了 extgcd拓展欧几里德算法 用于求解 ax+by=gcd(a,b)的解 这个要多说一下 ax+by=c,(a,b,c都是常数) 这就是一个直线方程嘛!(x,y)就是一条直线的轨迹 但是呢  我们在计算机中经常要求一些离散的东西,也就是我们要求整数解 这个咋求呢,就可以用这个神仙方法extgcd可以来求 我们可以用extgcd求解 ax+by=c的整数点的解 extgcd的内容呢:extgcd可以能求gcd----原理嘛,自己

欧几里德算法gcd及其拓展终极解释

这个困扰了自己好久,终于找到了解释,还有自己改动了一点点,耐心看完一定能加深理解 扩展欧几里德算法-求解不定方程,线性同余方程. 设过s步后两青蛙相遇,则必满足以下等式: (x+m*s)-(y+n*s)=k*l(k=0,1,2....) 稍微变一下形得: (n-m)*s+k*l=x-y 令n-m=a,k=b,x-y=c,即 a*s+b*l=c 只要上式存在整数解,则两青蛙能相遇,否则不能. 首先想到的一个方法是用两次for循环来枚举s,l的值,看是否存在s,l的整数解,若存在则输入最小的s, 但

POJ 2773 Happy 2006(欧几里德算法)

题意:给出一个数m,让我们找到第k个与m互质的数. 方法:这题有两种方法,一种是欧拉函数+容斥原理,但代码量较大,另一种办法是欧几里德算法,比较容易理解,但是效率很低. 我这里使用欧几里德算法,欧几里德算法又名辗转相除法,原先单纯的用于求最大公约数,这里也算是一个小小的拓展应用,这个题利用的欧几里德算法的重要性质,假如a与b互质,那么b*t+a与b也一定互质,那样我们可以枚举1-m之间所有符合条件的数,然后打一个表格,求出所有符合条件的数,正如下表中的(5,5)所示,这个表格是一个带有周期性的自

扩展欧几里德算法

文章来源:http://blog.csdn.net/zhjchengfeng5/article/details/7786595 谁是欧几里德?自己百度去 先介绍什么叫做欧几里德算法 有两个数 a b,现在,我们要求 a b 的最大公约数,怎么求?枚举他们的因子?不现实,当 a b 很大的时候,枚举显得那么的na?ve ,那怎么做? 欧几里德有个十分又用的定理: gcd(a, b) = gcd(b , a%b) ,这样,我们就可以在几乎是 log 的时间复杂度里求解出来 a 和 b 的最大公约数了

【poj 1061】青蛙的约会(数论--同余方程 拓展欧几里德)

题意:已知2只青蛙的起始位置 a,b 和跳跃一次的距离 m,n,现在它们沿着一条长度为 l 的纬线(圈)向相同方向跳跃.问它们何时能相遇?(好有聊的青蛙 (??????‵) *)永不相遇就输出"Impossible".(蠢得可怜 -_-!) 解法:用拓展欧几里德求同余方程的最小正整数解.(a+mx)-(b+nx)=k*l (k表示圈数) → (m-n)x=k*l+b-a → (m-n)x=b-a(mod l).当然其实=(b-a)%l 更准确,但反正都是模,也没有关系啦.于是就像上题一

欧几里德与扩展欧几里德算法(转)

欧几里德算法 欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数. 基本算法:设a=qb+r,其中a,b,q,r都是整数,则gcd(a,b)=gcd(b,r),即gcd(a,b)=gcd(b,a%b). 第一种证明: a可以表示成a = kb + r,则r = a mod b 假设d是a,b的一个公约数,则有 d|a, d|b,而r = a - kb,因此d|r 因此d是(b,a mod b)的公约数 假设d 是(b,a mod b)的公约数,则 d | b , d |r ,但是a

HDU 1098 Ignatius&#39;s puzzle 费马小定理+扩展欧几里德算法

题目大意: 给定k,找到一个满足的a使任意的x都满足 f(x)=5*x^13+13*x^5+k*a*x 被65整除 推证: f(x) = (5*x^12 + 13 * x^4 + ak) * x 因为x可以任意取 那么不能总是满足 65|x 那么必须是 65 | (5*x^12 + 13 * x^4 + ak) 那么就是说 x^12 / 13 + x^4 / 5 + ak / 65 正好是一个整数 假设能找到满足的a , 那么将 ak / 65 分进x^12 / 13 + x^4 / 5中得到