算法设计与分析-Greedy 「国科大」卜东波老师

1.Question Number 1: Commando War

There is a war and it doesn’t look very promising for your country. Now it’s time to act. You have a commando squad at your disposal and planning an ambush on an important enemy camp located nearby. You have N soldiers in your squad. In your master-plan, every single soldier has a unique responsibility and you don’t want any of your soldier to know the plan for other soldiers so that everyone can focus on his task only. In order to enforce this, you brief every individual soldier about his tasks separately and just before sending him to the battlefield. You know that every single soldier needs a certain amount of time to execute his job. You also know very clearly how much time you need to brief every single soldier. Being anxious to finish the total operation as soon as possible, you need to find an order of briefing your soldiers that will minimize the time necessary for all the soldiers to complete their tasks. You may assume that, no soldier has a plan that depends on the tasks of his fellows. In other words, once a soldier begins a task, he can finish it without the necessity of pausing in between.
Input: There will be multiple test cases in the input file. Every test case starts with an integer N(1 <= N <= 1000), denoting the number of soldiers. Each of the following N lines describe a soldier with two integers B(1 <= B <= 10000)&J(1 <= J <= 10000). B seconds are needed to brief the soldier while completing his job needs J seconds. The end of input will be denoted by a case with N = 0 . This case should not be processed.
Output: For each test case, print a line in the format, “Case X: Y ”, where X is the case number &Y is the total number of seconds counted from the start of your first briefing till the completion of all jobs.

翻译如下:

这是一场战争,情况对你的国家来说并不乐观。现在是采取行动的时候了。你手头有一个突击队,计划在附近伏击一个重要的敌方营地。你的小队有N名士兵。在你的总体计划中,每个士兵都有独特的责任,你不希望任何士兵知道其他士兵的计划,这样每个人都只能专注于他的任务。为了执行这一点,你需要单独向每个士兵介绍他的任务,并且在派他上战场之前。你知道每个士兵执行他的工作需要一定的时间。你也非常清楚你需要多少时间来简报每个士兵。由于迫切希望尽快完成整个行动,你需要找到一个简报士兵的顺序,以最小化所有士兵完成任务所需的时间。你可以假设,没有士兵的计划依赖于他同伴的任务。换句话说,一旦一个士兵开始任务,他可以完成它,无需中途暂停。

输入:输入文件中将有多个测试案例。每个测试案例以一个整数N(1 <= N <= 1000)开始,表示士兵的数量。接下来的N行描述了一个士兵,有两个整数B(1 <= B <= 10000)和J(1 <= J <= 10000)。需要B秒来简报士兵,而完成他的工作需要J秒。输入的结束将以N = 0的案例表示。不需要处理这个案例。

输出:对于每个测试案例,打印一行格式为“Case X: Y”的内容,其中X是案例编号,Y是从你开始第一次简报到完成所有工作所计算的总秒数。

1. 算法描述

自然语言描述
  • 将每个士兵的简报时间和任务执行时间作为一个单位来考虑。
  • 根据每个士兵完成任务的时间(即J值)以降序方式对士兵进行排序。
  • 按照排序后的顺序对士兵进行简报。
  • 在一个士兵完成任务之后,立即开始下一个士兵的任务(如果有的话)。
伪代码
# 定义计算最小总时间的函数,接收士兵列表作为参数
function calculateMinTime(soldiers):
    # 根据每个士兵执行任务所需的时间(J值)降序排序士兵
    sort soldiers by J in descending order

    # 初始化总时间和简报时间
    totalTime = 0
    briefingTime = 0

    # 遍历排序后的士兵列表
    for each soldier in soldiers:
        # 累加当前士兵的简报时间
        briefingTime += soldier.B

        # 更新总时间。总时间是当前的简报时间加上这个士兵完成任务的时间,
        # 和之前的总时间取较大值,因为任务可以并行执行
        totalTime = max(totalTime, briefingTime + soldier.J)

    # 返回计算出的最小总时间
    return totalTime

2. 最优子结构和动态规划方程

  • 最优子结构:每次选择剩余士兵中任务执行时间最长的士兵进行简报,是局部最优选择。
  • 动态规划方程:并不适用于此问题,因为该问题是贪心算法问题,并非动态规划问题。

3. 算法正确性证明

  • 贪心选择性质:选择任务执行时间最长的士兵首先进行简报,可以确保总等待时间最小。因为较长的任务在等待较短任务完成时已部分或完全进行。
  • 最优子结构:由于士兵的任务是独立的,每次选择都是独立且不影响其它选择,因此局部最优解能导向全局最优解。

4. 算法复杂度分析

  • 时间复杂度:主要花费在排序上,为O(NlogN),其中N是士兵的数量。
  • 空间复杂度:O(N),用于存储士兵的信息。

这种方法通过在每个步骤做出局部最优选择(即选择任务执行时间最长的士兵)来获得全局最优解,且由于问题的独立性质,这种贪心策略是有效的。

2.Question Number 2: DNA Consensus String

The Hamming distance is the number of different characters at each position from two strings of equal length. For example, assume we are given the two strings “AGCAT” and “GGAAT.” The Hamming distance of these two strings is 2 because the 1st and the 3rd characters of the two strings are different. Using the Hamming distance, we can define a representative string for a set of multiple strings of equal length. Given a set of strings S = {s1,…,sm} of length n, the consensus error between a string y of length n and the set S is the sum of the Hamming distances between y and each si in S. If the consensus error between y and S is the minimum among all possible strings y of length n, y is called a consensus string of S. For example, given the three strings “AGCAT”
“AGACT” and “GGAAT” the consensus string of the given strings is “AGAAT” because the sum of the Hamming distances between “AGAAT” and the three strings is 3 which is minimal. (In this case, the consensus string is unique, but in general, there can be more than one consensus string.) We use the consensus string as a representative of the DNA sequence. For the example of Figure 1 above, a consensus string of gene X is “GCAAATGGCTGTGCA” and the consensus error is 7.
Input: Your program is to read from standard input. The input consists of T test cases. The number of test cases T is given in the first line of the input. Each test case starts with a line containing two integers m and n which are separated by a single space. The integer m(4 ≤ m ≤ 50) represents the number of DNA sequences and n(4 ≤ n ≤ 1000) represents the length of the DNA sequences, respectively. In each of the next m lines, each DNA sequence is given.
Output: Your program is to write to standard output. Print the consensus string in the first line of each case and the consensus error in the second line of each case. If there exists more than one consensus string, print the lexicographically smallest consensus string.

算法设计与分析-Greedy 「国科大」卜东波老师_第1张图片

翻译如下:

DNA共识字符串
汉明距离是指两个等长字符串在每个位置上不同字符的数量。例如,假设我们有两个字符串“AGCAT”和“GGAAT”。这两个字符串的汉明距离是2,因为这两个字符串的第1个和第3个字符是不同的。使用汉明距离,我们可以为一组等长的多个字符串定义一个代表性的字符串。给定一组长度为n的字符串S = {s1,…,sm},字符串y(长度为n)与集合S的共识错误是y与S中每个si之间的汉明距离之和。如果字符串y与S的共识错误在所有可能的长度为n的字符串y中是最小的,则称y是S的共识字符串。例如,给定三个字符串“AGCAT”、“AGACT”和“GGAAT”,给定字符串的共识字符串是“AGAAT”,因为“AGAAT”与这三个字符串之间的汉明距离之和是3,这是最小的。(在这种情况下,共识字符串是唯一的,但通常可能有不止一个共识字符串。)我们使用共识字符串作为DNA序列的代表。对于上图1中的例子,基因X的共识字符串是“GCAAATGGCTGTGCA”,共识错误是7。
输入:您的程序要从标准输入读取。输入包含T个测试案例。测试案例的数量T在输入的第一行给出。每个测试案例以包含两个由单个空格分隔的整数m和n的行开始。整数m(4 ≤ m ≤ 50)代表DNA序列的数量,n(4 ≤ n ≤ 1000)分别代表DNA序列的长度。在接下来的m行中,给出了每个DNA序列。
输出:您的程序要写入标准输出。在每个案例的第一行打印共识字符串,在每个案例的第二行打印共识错误。如果存在多个共识字符串,请打印字典序最小的共识字符串。

要解决这个问题,我们需要设计一个算法来找到一组DNA序列的共识字符串,并计算共识错误。以下是解决这个问题的步骤:

1. 算法描述

自然语言描述
  • 对于每个位置i(1 <= i <= n),统计集合S中所有字符串在该位置的字符出现频率。
  • 对于每个位置i,选择频率最高的字符作为共识字符串在该位置的字符。
  • 如果有多个字符频率相同且最高,选择字典序最小的字符。
  • 计算共识字符串与集合S中每个字符串的汉明距离之和,即共识错误。
伪代码
# 定义寻找共识字符串的函数,输入参数为DNA序列集合以及序列的数量m和序列长度n
function findConsensusString(DNA_sequences, m, n):
    consensusString = "" # 初始化共识字符串为空
    consensusError = 0 # 初始化共识错误为0

    # 对于每个序列位置i进行遍历
    for i from 1 to n:
        frequencyMap = create a map to count character frequencies at position i # 创建一个映射来统计位置i的字符频率
        # 遍历每个DNA序列
        for each sequence in DNA_sequences:
            increase frequency of the character at position i in frequencyMap # 在频率映射中增加位置i的字符的频率
        maxFrequencyChar = find the character with maximum frequency # 找到频率最高的字符
        consensusString += maxFrequencyChar # 将频率最高的字符加到共识字符串中
        # 再次遍历每个DNA序列来计算共识错误
        for each sequence in DNA_sequences:
            if sequence[i] != maxFrequencyChar: # 如果位置i的字符不是频率最高的字符
                consensusError += 1 # 共识错误加1

    # 返回共识字符串和共识错误
    return consensusString, consensusError

2. 最优子结构和DP方程

在这个问题中,我们没有明确的最优子结构和动态规划方程,因为这是一个基于频率计数的贪心算法问题,而非动态规划问题。

3. 算法正确性证明

  • 通过选择每个位置上频率最高的字符,我们确保了这个位置上的汉明距离之和最小,因为更多的序列在这个位置上有相同的字符。
  • 通过整个序列上重复此过程,我们可以保证整个序列的共识错误是最小的。

4. 算法复杂度分析

  • 时间复杂度:对于每个位置,我们需要O(m)时间来计算频率并找到频率最高的字符,然后需要再次O(m)时间来计算共识错误。因此总的时间复杂度是O(nm),其中n是序列长度,m是序列数量。
  • 空间复杂度:我们需要O(n)的空间来存储共识字符串,和一个额外的空间来存储每个位置的字符频率。因此总的空间复杂度是O(n)。

3.Question Number 3: Opponents

Arya has n opponents in the school. Each day he will fight with all opponents who are present this day. His opponents have some fighting plan that guarantees they will win, but implementing this plan requires presence of them all. That means if one day at least one of Arya’s opponents is absent at the school, then Arya will beat all present opponents. Otherwise, if all opponents are present, then they will beat Arya.
For each opponent Arya knows his schedule — whether or not he is going to present on each particular day. Tell him the maximum number of consecutive days that he will beat all present opponents.
Note, that if some day there are no opponents present, Arya still considers he beats all the present opponents.
Input: The first line of the input contains two integers n and d ( 1 <= n,d <= 100 ) — the number of opponents and the number of days, respectively. The i -th of the following d lines contains a string of length n consisting of characters ’0’ and ’1’. The j -th character of this string is ’0’ if the j -th opponent is going to be absent on the i -th day.
Output: Print the only integer — the maximum number of consecutive days that Arya will beat all present opponents.

这个问题是关于确定一个人能连续多少天在学校里击败所有到场的对手。下面是问题的详细描述和算法的中文翻译:

问题描述

阿里亚在学校里有n个对手。每天他都要与所有到场的对手战斗。他的对手们有一套作战计划,只要他们全部到场就能保证胜利。这意味着,如果有一天至少有一个对手缺席,阿里亚就会打败所有到场的对手。否则,如果所有对手都到场,他们就会打败阿里亚。
对于每个对手,阿里亚都知道他的时间表——他是否会在特定的每一天出现。告诉他他能连续打败所有到场对手的最大天数。
请注意,如果某天没有对手出现,阿里亚仍然认为他打败了所有到场的对手。

输入

输入的第一行包含两个整数n和d(1 <= n,d <= 100)——分别是对手的数量和天数。接下来的d行中,第i行包含一个长度为n的字符串,由字符’0’和’1’组成。这个字符串的第j个字符是’0’,如果第j个对手在第i天将要缺席。

输出

打印一个整数——阿里亚能连续打败所有到场对手的最大天数。

算法描述

自然语言描述
  • 创建一个整数计数器,用来跟踪最大连续天数。
  • 遍历每一天,检查是否有对手缺席。
  • 如果当天至少有一个对手缺席,增加连续天数计数器。
  • 如果所有对手都出现,重置连续天数计数器。
  • 保持记录连续天数计数器的最大值。
伪代码
function findMaxConsecutiveDays(schedule, n, d):
    maxConsecutiveDays = 0
    currentConsecutiveDays = 0

    for i from 1 to d:
        if '0' in schedule[i]:
            currentConsecutiveDays += 1
        else:
            currentConsecutiveDays = 0
        maxConsecutiveDays = max(maxConsecutiveDays, currentConsecutiveDays)

    return maxConsecutiveDays

最优子结构和DP方程

这个问题并不需要动态规划来解决,因为它可以通过简单的一次遍历得到答案。所以,没有明确的最优子结构和DP方程。

正确性证明

  • 在每一天,如果至少有一个对手缺席,阿里亚就能赢,所以这一天可以算作连续的一部分。
  • 如果所有对手都出现,则阿里亚会输,连续的天数中断。
  • 通过以上规则,我们可以保证计算的连续

天数是阿里亚能连续赢的最大天数。

复杂度分析

  • 时间复杂度:因为我们只需要遍历d天一次,所以时间复杂度为O(d)。
  • 空间复杂度:除了输入的存储,我们只需要常数级别的额外空间来存储maxConsecutiveDayscurrentConsecutiveDays,因此空间复杂度为O(1)。

综上所述,这个算法通过一次遍历来确定最大连续天数,既高效又简洁。

4.Question Number 4: Minimum Varied Number

Find the minimum number with the given sum of digits s such that all digits in it are distinct (i.e. all digits are unique).
For example, if s = 20 , then the answer is 389 . This is the minimum number in which all digits are different and the sum of the digits is 20 ( 3 + 8 + 9 = 20 ).
For the given s print the required number.
Input: The first line contains an integer t ( 1 ≤ t ≤ 45 ) — the number of test cases. Each test case is specified by a line that contains the only integer s ( 1 ≤ s ≤ 45 ).
Output: Print t integers — the answers to the given test cases.

翻译如下:

找到给定数字和s的最小数,且该数的所有数字都是不同的(即所有数字都是唯一的)。
例如,如果s = 20,那么答案是389。这是所有数字都不同且数字和为20的最小数(3 + 8 + 9 = 20)。
对于给定的s,打印所需的数字。
输入:第一行包含一个整数t(1 ≤ t ≤ 45)——测试案例的数量。每个测试案例由一行指定,该行包含唯一的整数s(1 ≤ s ≤ 45)。
输出:打印t个整数 —— 给定测试案例的答案。

为了找到给定数字之和为s的最小不同数字组合,我们可以使用贪心算法,从最大的单个数字9开始,向下逐步添加小一点的数字,直到它们的和为s。这种方法保证了我们得到的数字尽可能小,因为较大的数字位于较高的位上。

算法描述

自然语言描述
  • 初始化一个空字符串来构建答案。
  • 从9开始向下遍历数字,直到0。
  • 对于每个数字,如果将它添加到目前的和中不会超过s,那么就将其添加到答案中,并从s中减去这个数字的值。
  • 继续这个过程,直到s减到0。
  • 最后,将构建的答案字符串翻转(因为我们是从高位开始添加数字的,而我们需要从低位开始的数字)。
伪代码
function findMinimumVariedNumber(s):
    answer = ""
    for digit in range(9, 0, -1):
        while s >= digit and not digit in answer:
            answer += str(digit)
            s -= digit
    return answer[::-1]  # Reverse the answer

最优子结构和DP方程

此问题不需要动态规划解决,因为可以通过贪心算法有效解决。

正确性证明

贪心算法有效,因为我们总是优先选择最大的数字,并确保不重复选择。这样可以保证组成的数字尽可能小(因为较大的数字被放在了更低的位上)。

复杂度分析

  • 时间复杂度:对于每个测试用例,我们最多需要检查9次,所以时间复杂度为O(9t)或简化为O(t),因为9是一个常数。
  • 空间复杂度:我们只需要存储一个字符串以及几个整数变量,所以空间复杂度为O(1

)。

注:时间复杂度的精确计算

在最坏情况下,对于每个测试用例,我们需要从9遍历到1,并且可能每个数字只能使用一次,所以实际的操作步骤是与数字的范围有关,即O(9)。但是,由于我们每次循环都会减少s的值,总的操作次数实际上会小于等于s的值。因此,对于所有测试用例,总的时间复杂度是O(ts),其中t是测试用例的数量,s是数字之和的上限。

实际应用

根据这个算法,如果我们要找到数字和为20的最小不同数字组合,我们会从9开始,因为9是最大的单个数字,然后是8,然后是3(因为2已经不能再使用了,否则会超过20)。所以,答案是389,和为20。

我们可以实现这个算法,并且为每个输入的测试用例s计算出答案。

5.Question Number 5: Joey Takes Money

Joey is low on money. His friend Chandler wants to lend Joey some money, but can’t give him directly, as Joey is too proud of himself to accept it. So, in order to trick him, Chandler asks Joey to play a game.
In this game, Chandler gives Joey an array a1,a2,…,an ( n ≥ 2 ) of positive integers ( ai ≥ 1 ). Joey can perform the following operation on the array any number of times:

1.Take two indices i and j ( 1 ≤ i < j ≤ n) . 2. Choose two integers x and y ( x, y ≥ 1 ) such that x·y=ai·aj . 3. Replaceai byxandaj byy.

In the end, Joey will get the money equal to the sum of elements of the final array.
Find the maximum amount of money ans Joey can get but print 2022 · ans . Why multiplied by
2022 ? Because we are never gonna see it again!
It is guaranteed that the product of all the elements of the array a doesn’t exceed 1012 .

Input: Each test contains multiple test cases. The first line contains the number of test cases t ( 1 ≤ t ≤ 4000 ). Description of the test cases follows. The first line of each test case contains a single integer n ( 2 ≤ n ≤ 50 ) — the length of the array a . The second line contains n integers a1,a2,…,an ( 1 ≤ ai ≤ 106 ) — the array itself. It’s guaranteed that the product of all ai doesn’t exceed1012 (i. e. a1·a2·…·an ≤1012 ).

Output: For each test case, print the maximum money Joey can get multiplied by 2022.

翻译如下:

乔伊资金紧张。他的朋友钱德勒想借给乔伊一些钱,但不能直接给他,因为乔伊太自尊了,不愿接受。所以,为了欺骗他,钱德勒让乔伊玩一个游戏。
在这个游戏中,钱德勒给乔伊一个由正整数组成的数组a1,a2,…,an(n ≥ 2,每个ai ≥ 1)。乔伊可以对数组执行以下操作任意次数:

  1. 选取两个索引i和j(1 ≤ i < j ≤ n)。
  2. 选择两个整数x和y(x, y ≥ 1),使得x·y=ai·aj。
  3. 将ai替换为x,将aj替换为y。
    最终,乔伊将获得等于最终数组元素之和的金额。
    找到乔伊能得到的最大金额ans,并打印2022·ans。为什么乘以2022?因为我们再也不会见到它了!
    保证所有数组元素的乘积不会超过10^12。
    输入:每个测试包含多个测试案例。第一行包含测试案例的数量t(1 ≤ t ≤ 4000)。测试案例的描述如下。每个测试案例的第一行包含一个整数n(2 ≤ n ≤ 50)——数组a的长度。第二行包含n个整数a1,a2,…,an(1 ≤ ai ≤ 106)——数组本身。保证所有ai的乘积不会超过1012(即a1·a2·…·an ≤ 10^12)。
    输出:对于每个测试案例,打印乔伊能获得的最大金额乘以2022。

为了解决这个问题,我们需要找到一种方式,让乔伊通过对数组元素进行操作来获得最大的金额。在这个游戏中,乔伊可以执行一种操作,即选择数组中的两个元素aiaj,并将它们替换为两个乘积相等的正整数xy。目标是最大化数组元素之和。

我们知道,两个数的乘积在数学上等于其因子的和的最大值时,这两个数必须相等。例如,数6可以分解为3和2,其和为5;但如果我们分解为1和6或者6和1,其和为7,是最大的。所以,为了最大化和,我们应该始终将aiaj分解为两个相等的数。

让我们先转换问题:我们需要找到一个乘积为ai*aj的数对xy,使得x+y最大。显然,当x=y=sqrt(ai*aj)时,x+y是最大的,因为当两个正数的乘积固定时,它们的和在它们相等时最大。

现在,我们可以描述算法:

算法描述

  1. 遍历数组中的所有数对aiaj
  2. 对于每对数,计算ai*aj的平方根,向下取整到最近的整数,记为k
  3. aiaj替换为k
  4. 重复以上步骤直到不能再进行任何操作。
  5. 计算最终数组的元素之和,乘以2022。

但是,由于我们知道最优的操作是将数对替换为它们乘积的平方根,实际上我们不需要执行这个操作。我们只需要知道我们可以这样做。所以,我们只需计算数组的所有元素的乘积的平方根之和,乘以2022即可。

伪代码

function calculateMaxMoney(array):
    total_product = 1
    for ai in array:
        total_product *= ai

    # 如果总乘积是一个完全平方数,则其平方根是最大整数k
    k = floor(sqrt(total_product))

    # 计算最终答案
    ans = n * k  # 因为每个数组元素都被替换为了k
    return 2022 * ans

复杂度分析

  • 时间复杂度:计算数组元素乘积的时间复杂度为O(n),求平方根为O(1),因此总时间复杂度为O(n)。
  • 空间复杂度:只需要常数空间来存储总乘积和最终答案,所以是O(1)。

输出

对于每个测试用例,输出2022 * ans

注意:由于题目保证了数组元素乘积不超过10^12,我们不需要担心整数溢出问题。如果乘积可能非常大,需要使用高精度算术来处理可能的大数问题。在实际编程中,通常可以使用语言提供的大整数库或者自己实现大数运算。

你可能感兴趣的:(算法设计与分析,计算机基础,算法)