1152. The False Mirrors
Time Limit: 2.0 second
Memory Limit: 16 MB
Background
Problem
Input
Output
Sample
input | output |
---|---|
7 3 4 2 2 1 4 1 |
9 |
Problem Source: Ural Collegiate Programming Contest, April 2001, Perm, English Tour
解答如下:
2 using System.IO;
3 using System.Text.RegularExpressions;
4
5 namespace Skyiv.Ben.Timus
6 {
7 // http://acm.timus.ru/problem.aspx?space=1&num=1152
8 class T1152
9 {
10 static void Main()
11 {
12 new T1152().Run(Console.In, Console.Out);
13 }
14
15 void Run(TextReader reader, TextWriter writer)
16 {
17 reader.ReadLine();
18 string[] ss = Regex.Split(reader.ReadLine().Trim(), @"\s+");
19 int[] a = new int[ss.Length]; // 阳台上的怪物数
20 int[] m = new int[ss.Length]; // 阳台被击中的次数
21 int sum = 0, damage = 0, min = int.MaxValue;
22 for (int i = 0; i < a.Length; i++) sum += a[i] = int.Parse(ss[i]);
23 writer.WriteLine(GetDamage(a, m, ref sum, ref damage, min));
24 }
25
26 int GetDamage(int[] a, int[] m, ref int sum, ref int damage, int min)
27 {
28 if (sum == 0) return (min < damage) ? min : damage; // sum: 幸存的怪物个数
29 for (int i = 0; i < a.Length; i++) // 遍历幸存的阳台
30 {
31 if (m[i] != 0) continue; // m[i]: 第 i 个阳台被击中的次数
32 damage += sum = Shoot(a, m, sum, i, 1, 1); // 主角开枪后怪物还击
33 if (min > damage) min = GetDamage(a, m, ref sum, ref damage, min);
34 damage -= sum; // 回溯,还原主角受到的伤害(damage)
35 sum = Shoot(a, m, sum, i, -1, 0); // 回溯,还原阳台(m)及其上的怪物(sum)
36 }
37 return min; // 消灭全部怪物后主角所受到的最小伤害
38 }
39
40 int Shoot(int[] a, int[] m, int sum, int i, int sign, int cnt)
41 {
42 for (int j = i; j <= i + 2; j++) // 主角每枪击毁三个相邻的阳台
43 if (cnt == (m[j % a.Length] += sign)) sum -= sign * a[j % a.Length];
44 return sum; // 幸存的怪物个数
45 }
46 }
47 }
故事中的主角周围环绕着一圈 N (3 ≤ N ≤ 20) 个阳台,每个阳台上有若干个怪物。主角每开一枪能够摧毁相邻的三个阳台,然后幸存的怪物还击,假设每个怪物每次攻击对主角的伤害都是一个单位。然后开始新一轮枪战,直到消灭所有的怪物。你的任务就是计算出主角受到的最小伤害。
该程序调用 GetDamage() 方法来计算主角所受到的伤害(第 23 行)。该方法首先判断是否还有幸存的阳台(第 28 行),如有的话就遍历幸存的阳台(第 29 行),在循环中调用 Shoot() 方法模拟主角开枪后怪物还击(第 32 行),如果这一轮枪战之后主角所受的伤害(damage)小于消灭全部怪物后主角所受到的最小伤害(min)的话(第 33 行),就递归地调用 GetDamage() 方法自身直到消灭全部怪物(第 33 行)。程序中第 34 和 35 行是 GetDamage() 方法递归调用返回后的回溯,还原主角受到的伤害(damage)和阳台(m)及其上的怪物(sum),以便循环进入下一轮枪战。
下图是在程序的第 27 行之后 ( GetDamage() 方法的入口点 ) 和第 33 行之后 ( GetDamage() 方法递归调用后的返回点 ) 插入适当的调试语句后,经整理后得到的,显示了程序的运行情况。
这个程序的 C++ 语言版本如下:
2
3 int n, a[20], m[20];
4
5 int Shoot(int a[], int m[], int sum, int i, int sign, int cnt)
6 {
7 for (int j = i; j <= i + 2; j++) // 主角每枪击毁三个相邻的阳台
8 if (cnt == (m[j % n] += sign)) sum -= sign * a[j % n];
9 return sum; // 幸存的怪物个数
10 }
11
12 int GetDamage(int a[], int m[], int& sum, int& damage, int min)
13 {
14 if (sum == 0) return (min < damage) ? min : damage; // sum: 幸存的怪物个数
15 for (int i = 0; i < n; i++) // 遍历幸存的阳台
16 {
17 if (m[i] != 0) continue; // m[i]: 第 i 个阳台被击中的次数
18 damage += sum = Shoot(a, m, sum, i, 1, 1); // 主角开枪后怪物还击
19 if (min > damage) min = GetDamage(a, m, sum, damage, min);
20 damage -= sum; // 回溯,还原主角受到的伤害(damage)
21 sum = Shoot(a, m, sum, i, -1, 0); // 回溯,还原阳台(m)及其上的怪物(sum)
22 }
23 return min; // 消灭全部怪物后主角所受到的最小伤害
24 }
25
26 int main()
27 {
28 std::cin >> n;
29 int sum = 0, damage = 0, min = INT_MAX;
30 for (int i = 0; i < n; i++)
31 {
32 std::cin >> a[i];
33 sum += a[i];
34 }
35 std::cout << GetDamage(a, m, sum, damage, min) << std::endl;
36 }
本程序的运行时间是: C#: 0.39 秒,C++: 0.343 秒:
但这道题最好的运行时间是 0.001 秒:
有谁知道更好的算法吗?