银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

在上一篇随笔“回文数问题”中,要找出比指定的数大的最小回文数,而这个数可能包含有百万个十进制数字。所以不能使用大整数来求解。现在让我们来看看应该使用大整数来求解的几道ACM题,这些题目均来源于 Sphere Online Judge (SPOJ) 网站。

Not So Fast Multiplication

首先是一道大整数乘法的题目,主要内容如下所示:

Not So Fast Multiplication: Multiply the given numbers.

Input:
n [the number of multiplications <= 1,000]
a b [numbers to multiply (at most 10,000 decimal digits each)]
Text grouped in [ ] does not appear in the input file.

Output: The results of multiplications.

Warning: large Input/Output data, be careful with certain languages

Time limit: 12s

也就是说,要求在十二秒之内计算大约一千道的整数乘法,而参数与运算的每个整数大约都有一万个十进制数字。由于 Ruby 语言内置支持 Bignum ,所以程序非常简单,就是以下这么四行:

gets.to_i.downto(1) do
  a, b = gets.split
  puts a.to_i * b.to_i
end

在该网站提交,结果是“accepted”。运行时间是 10.19 秒,内存占用为 10 MB,目前在 RUBY 语言中排名第五位

相应的 F# 程序如下:

for _ in 1  .. int(System.Console.ReadLine()) do
  let ss = System.Console.ReadLine().Split()
  bigint.Parse(ss.[0]) * bigint.Parse(ss.[1]) |> printfn "%O"

在该网站提交,结果超时了。看来该网站的 F# 的性能不怎么样。

Fast Multiplication

这道题目的内容和上一道题目一样,仅是时间限制从十二秒改为两秒。在该网站提交上述 Ruby 程序,非常遗憾,结果是“time limit exceeded”。看来 Ruby 语言内置的 Bignum 类的乘法运算速度还不够快。 :(

我在2008年7月写过一篇随笔“再谈 BigInteger - 使用快速傅里叶变换”,用 C# 语言实现了 Skyiv.Numeric.BigInteger 类。那么,就让我们使用 C# 语言来求解这道题目吧:

01:  using System;
02:  using Skyiv.Numeric;
03:  
04:  namespace Skyiv.SphereOnlineJudge
05:  {
06:    // http://www.spoj.pl/problems/MUL/
07:    sealed class Multiplication
08:    {
09:      static void Main()
10:      {
11:        for (var i = int.Parse(Console.ReadLine()); i > 0; i--)
12:        {
13:          var ss = Console.ReadLine().Split();
14:          Console.WriteLine(BigInteger.Parse(ss[0]) * BigInteger.Parse(ss[1]));
15:        }
16:      }
17:    }
18:  }

在该网站提交,终于“accepted”了,运行时间是 1.49 秒,内存占用为 13 MB,目前在 C# 语言中排名第一位,也是唯一的一位。在 Microsoft .NET Framework 4 Base Class Library 中也有 System.Numerics.BigInteger 结构,其乘法是使用 Karatsuba 算法,其时间复杂度约为 O(N1.585),相关的源代码请参阅“浅谈 BigInteger”。而我的 Skyiv.Numeric.BigInteger 类的乘法是使用快速傅里叶变换,时间复杂度可降低到 O(N logN loglogN)。Sphere Onlile Judge (SPOJ) 网站使用的 C# 编译器是 Mono C# compiler version 2.0.1,并不支持 System.Numerics.BigInteger 结构。即使该网站支持 Microsoft(R) Visual C# 2010 编译器 4.0.30319.1 版,使用 .NET 4 内置的 BigInteger 来做这道题目也会和使用 Ruby 语言内置的 Bignum 一样超时。

Small factorials

这是一道求解 100 以内的数的阶乘的题目,主要内容如下所示:

Small factorials: You are asked to calculate factorials of some small positive integers.

Input: An integer t, 1<=t<=100, denoting the number of testcases, followed by t lines, each containing a single integer n, 1<=n<=100.

Output: For each integer n given at input, display a line with the value of n!

Time limit: 1s

Source limit: 2,000 Bytes

也就是说,要求在一秒之内计算出大约 100 个 100 以内的数的阶乘。注意阶乘增长是非常快的,100! 就有 158 位十进制数字。下面就是 Ruby 程序:

hash = {}
prod = 1
1.upto(100) do |i|
  prod *= i
  hash[i] = prod
end

gets.to_i.downto(1) do
  puts hash[gets.to_i]
end

在该网站提交后,结果是“accepted”,运行时间是 0.04 秒,内存占用为 4.7 MB。可以看出 Ruby 语言在进行比较小的数的乘法时还是很快的,因为它会根据数的大小自动在 Fixnum 和 Bignum 之间切换。

下面来看看 C# 程序吧:

01:  using System;
02:  
03:  namespace Skyiv.SphereOnlineJudge
04:  {
05:    // http://www.spoj.pl/problems/FCTRL2/
06:    sealed class SmallFactorial
07:    {
08:      static void Main()
09:      {
10:        var array = GetArray(100);
11:        for (var i = int.Parse(Console.ReadLine()); i > 0; i--)
12:          Console.WriteLine(array[int.Parse(Console.ReadLine())]);
13:      }
14:  
15:      static BigInteger[] GetArray(int n)
16:      {
17:        var array = new BigInteger[n + 1];
18:        BigInteger prod = 1;
19:        for (var i = 1; i <= n; i++)
20:        {
21:          prod *= i;
22:          array[i] = prod;
23:        }
24:        return array;
25:      }
26:    }
27:  
28:    sealed class BigInteger
29:    {
30:      int[] digits = new int[180];
31:  
32:      public BigInteger(int n)
33:      {
34:        digits[0] = n;
35:        if (digits[0] > 9) Format();
36:      }
37:  
38:      public BigInteger(BigInteger x)
39:      {
40:        Array.Copy(x.digits, digits, digits.Length);
41:      }
42:  
43:      public static implicit operator BigInteger(int x)
44:      {
45:        return new BigInteger(x);
46:      }
47:  
48:      public static BigInteger operator +(BigInteger x, BigInteger y)
49:      {
50:        BigInteger z = new BigInteger(x);
51:        for (int i = x.digits.Length - 1; i >= 0; i--) z.digits[i] = x.digits[i] + y.digits[i];
52:        z.Format();
53:        return z;
54:      }
55:  
56:      public static BigInteger operator *(BigInteger x, int y)
57:      {
58:        BigInteger z = new BigInteger(x);
59:        for (int i = x.digits.Length - 1; i >= 0; i--) z.digits[i] = x.digits[i] * y;
60:        z.Format();
61:        return z;
62:      }
63:  
64:      void Format()
65:      {
66:        for (int quotient = 0, i = 0; i < digits.Length; i++)
67:        {
68:          int numerator = digits[i] + quotient;
69:          quotient = numerator / 10;
70:          digits[i] = numerator % 10;
71:        }
72:      }
73:  
74:      public override string ToString()
75:      {
76:        int n = digits.Length - 1;
77:        while (n >= 0 && digits[n] == 0) n--;
78:        if (n < 0) return "0";
79:        char[] cs = new char[n + 1];
80:        for (int i = n; i >= 0; i--) cs[i] = (char)(digits[n - i] + '0');
81:        return new string(cs);
82:      }
83:    }
84:  }

注意,这道题目要求源程序的大小限制的 2,000 字节以内。如果没有这个限制的话,就很容易作弊,也就是事先计算好所有的 100 以内的数的阶乘,然后把这些计算出来的值放到源程序的一个静态字符串数组中,在程序中读取输入后直接查表输出就行了。而且,由于这个限制,就不能使用前面提到的使用快速傅里叶变换的 Skyiv.Numeric.BigInteger 类了。这里使用的是“Timus 1013. K-based numbers. Version 3”中的简单的只有加法和乘法的 BigInteger 类。

在该网站提交,结果也是“accepted”,运行时间是 0.17 秒,内存占用为 11 MB,目前在 C# 语言中排名第一位,仅领先第二名 0.01 秒。但是这个结果比 Ruby 语言的 0.04 秒慢了很多。

Factorial

这道题目的主要内容如下所示:

Factorial:

The most important part of a GSM network is so called Base Transceiver Station (BTS). These transceivers form the areas called cells(this term gave the name to the cellular phone) and every phone connects to the BTS with the strongest signal (in a little simplified view). Of course, BTSes need some attention and technicians need to check their function periodically.

ACM technicians faced a very interesting problem recently. Given a set of BTSes to visit, they needed to find the shortest path to visit all of the given points and return back to the central company building. Programmers have spent several months studying this problem but with no results. They were unable to find the solution fast enough. After a long time, one of the programmers found this problem in a conference article. Unfortunately, he found that the problem is so called "Travelling Salesman Problem" and it is very hard to solve. If we have N BTSes to be visited, we can visit them in any order, giving us N! possibilities to examine. The function expressing that number is called factorial and can be computed as a product 1.2.3.4....N. The number is very high even for a relatively small N.

The programmers understood they had no chance to solve the problem. But because they have already received the research grant from the government, they needed to continue with their studies and produce at least someresults. So they started to study behaviour of the factorial function.

For example, they defined the function Z. For any positive integer N, Z(N) is the number of zeros at the end of the decimal form of number N!. They noticed that this function never decreases. If we have two numbers N1<N2, then Z(N1) <= Z(N2). It is because we can never "lose" any trailing zero by multiplying by any positive number. We can only get new and new zeros. The function Zis very interesting, so we need a computer program that can determine its value efficiently.

Input: There is a single positive integer T on the first line of input (equal to about 100,000). It stands for the number of numbers to follow. Then there are T lines, each containing exactly one positive integer number N, 1 <= N <= 1,000,000,000.

Output: For every number N, output a single line containing the single non-negative integer Z(N).

Time limit: 6s

别看这道题目说了一大堆,其实大部分内容都是在讲一个有趣的故事,实际上只是要求计算指定的正整数的阶乘末尾有多少个零。但是要计算的数大约有十万个,而每个数可能达到十亿,并且需要在六秒之内计算完毕。天哪,阶乘函数可是增长得非常快的,十亿的阶乘有多少位十进制数字?我晕了!其实,这是一个经典问题,并不需要用到大整数,使用普通的 Int32 就足够了:

01:  using System;
02:  using System.IO;
03:  
04:  namespace Skyiv.SphereOnlineJudge
05:  {
06:    // http://www.spoj.pl/problems/FCTRL/
07:    class Factorial
08:    {
09:      static void Main()
10:      {
11:        new Factorial().Run(Console.In, Console.Out);
12:      }
13:  
14:      void Run(TextReader reader, TextWriter writer)
15:      {
16:        for (int t = int.Parse(reader.ReadLine()); t > 0; t--)
17:        {
18:          var n = int.Parse(reader.ReadLine());
19:          writer.WriteLine(FactorialTailZeros(n));
20:        }
21:      }
22:  
23:      int FactorialTailZeros(int n)
24:      {
25:        var sum = 0;
26:        while ((n /= 5) > 0) sum += n;
27:        return sum;
28:      }
29:    }
30:  }

在该网站提交后,运行结果是“accepted”,运行时间是 2.97 秒,内存占用为 11 MB。

下面来看看等效的 Ruby 程序吧:

def get_factorial_tail_zeros n
  zeros = 0
  zeros += n while (n /= 5) > 0
  zeros
end

gets.to_i.downto(1) do
  puts get_factorial_tail_zeros gets.to_i
end

在该网站提交后,结果也是“accepted”,运行时间是 2.18 秒,内存占用为 4.7 MB,目前在 Ruby 语言中排名第二位。这个 Ruby 程序的算法和前面的 C# 程序是一模一样的,但是源程序更短小,运行时间更短,内存占用更少,样样都比 C# 好。 :)

Adding Reversed Numbers

这道题目的主要内容如下所示:

Adding Reversed Numbers:

The Antique Comedians of Malidinesia prefer comedies to tragedies. Unfortunately, most of the ancient plays are tragedies. Therefore the dramatic advisor of ACM has decided to transfigure some tragedies into comedies. Obviously, this work is very hard because the basic sense of the play must be kept intact, although all the things change to their opposites. For example the numbers: if any number appears in the tragedy, it must be converted to its reversed form before being accepted into the comedy play.

Reversed number is a number written in arabic numerals but the order of digits is reversed. The first digit becomes last and vice versa. For example, if the main hero had 1245 strawberries in the tragedy, he has 5421 of them now. Note that all the leading zeros are omitted. That means if the number ends with a zero, the zero is lost by reversing (e.g. 1200 gives 21). Also note that the reversed number never has any trailing zeros.

ACM needs to calculate with reversed numbers. Your task is to add two reversed numbers and output their reversed sum. Of course, the result is not unique because any particular number is a reversed form of several numbers (e.g. 21 could be 12, 120 or 1200 before reversing). Thus we must assume that no zeros were lost by reversing (e.g. assume that the original number was 12).

Input: The input consists of N cases (equal to about 10,000). The first line of the input contains only positive integer N. Then follow the cases. Each case consists of exactly one line with two positive integers separated by space. These are the reversed numbers you are to add.

Output: For each case, print exactly one line containing only one integer - the reversed sum of two reversed numbers. Omit any leading zeros in the output.

Time limit: 5s

讲述古代的洗具和杯具的故事,为了把杯具转换为洗具,需要把一些(很大的)整数倒转过来。题目给出大约一万对已经倒转了整数,要求计算出每一对整数的和,然后再倒转。好了,下面就是我们的 Ruby 程序:

def reverse n
  n.to_s.reverse.to_i
end

gets.to_i.downto(1) do
  a, b = gets.split
  puts reverse reverse(a.to_i) + reverse(b.to_i)
end

在该网站提交后,运行结果是“accepted”,运行时间是 0.49 秒,内存占用为 4.7 MB。这道题目只涉及大整数的加法和字符串的反转。

下面来看看 C# 程序吧:

01:  using System;
02:  
03:  namespace Skyiv.SphereOnlineJudge
04:  {
05:    // http://www.spoj.pl/problems/ADDREV/
06:    sealed class AddingReversedNumbers
07:    {
08:      static void Main()
09:      {
10:        for (var i = int.Parse(Console.ReadLine()); i > 0; i--)
11:        {
12:          var ss = Console.ReadLine().Split();
13:          Console.WriteLine(ReverseAdd(ss[0], ss[1]));
14:        }
15:      }
16:  
17:      static string ReverseAdd(string a, string b)
18:      {
19:        if (a.Length < b.Length) Swap(ref a, ref b);
20:        var sum = new char[a.Length + 1];
21:        int carry, i;
22:        for (carry = i = 0; i < a.Length; i++)
23:        {
24:          var c = a[i] + carry;
25:          if (i < b.Length) c += b[i] - '0';
26:          sum[i] = (char)((c > '9') ? (c - 10) : c);
27:          carry = (c > '9') ? 1 : 0;
28:        }
29:        if (carry == 1) sum[i] = '1';
30:        return new string(sum).TrimStart('0').TrimEnd('\0');
31:      }
32:  
33:      static void Swap<T>(ref T a, ref T b)
34:      {
35:        T c = a;
36:        a = b;
37:        b = c;
38:      }
39:    }
40:  }

上述程序中,没有使用 BigInteger 和字符串反转,而是直接对输入的字符串进行 ReverseAdd (第 17 到 31 行)。在该网站提交后,运行结果是“accepted”,运行时间是 0.57 秒,内存占用为 11 MB。

Julka

这道题目的主要内容如下所示:

Julka:

Julka surprised her teacher at preschool by solving the following riddle:

Klaudia and Natalia have 10 apples together, but Klaudia has two apples more than Natalia. How many apples does each of he girls have?

Julka said without thinking: Klaudia has 6 apples and Natalia 4 apples. The teacher tried to check if Julka's answer wasn't accidental and repeated the riddle every time increasing the numbers. Every time Julka answered correctly. The surprised teacher wanted to continue questioning Julka, but with big numbers she could't solve the riddle fast enough herself. Help the teacher and write a program which will give her the right answers.

Task:Write a program which

  • reads from standard input the number of apples the girls have together and how many more apples Klaudia has,
  • counts the number of apples belonging to Klaudia and the number of apples belonging to Natalia,
  • writes the outcome to standard output

Input: Ten test cases (given one under another, you have to process all!). Every test case consists of two lines. The first line says how many apples both girls have together. The second line says how many more apples Klaudia has. Both numbers are positive integers. It is known that both girls have no more than 10100 (1 and 100 zeros) apples together. As you can see apples can be very small.

Output: For every test case your program should output two lines. The first line should contain the number of apples belonging to Klaudia. The second line should contain the number of apples belonging to Natalia.

Time limit: 2s

这次讲述几个小女孩和苹果的故事。要求我们计算的大整数大约有 100 个十进制数字。Ruby 程序如下所示:

1.upto(10) do
  sum = gets.to_i
  diff = gets.to_i
  puts (sum + diff) / 2
  puts (sum - diff) / 2
end

在该网站提交后,运行结果是“accepted”,运行时间是 0.03 秒,内存占用为 4.7 MB。这道题目只涉及大整数的加法和减法。

相应的 F# 程序如下所示:

for _ in 1  .. 10 do
  let sum = bigint.Parse(System.Console.ReadLine())
  let diff = bigint.Parse(System.Console.ReadLine())
  (sum + diff) / 2I |> printfn "%O"
  (sum - diff) / 2I |> printfn "%O"

在该网站提交后,运行结果是“accepted”,运行时间是 0.48 秒,内存占用为 12 MB。看来该网站的 F# 的性能比不上 Ruby。

posted on 2010-07-05 22:40  银河  阅读(4330)  评论(13编辑  收藏  举报