贪心算法的应用

贪心算法的应用

如果想应用贪心算法,那么当下的情景应该包括以下两个特征:

  • 无后效性:当前决策不影响后续状态
  • 最优子结构:局部最优解能推导全局最优解

因此适用于贪心算法的问题往往具有明确的排序性质可分解性

P8597 [蓝桥杯 2013 省 B] 翻硬币

题目背景

小明正在玩一个“翻硬币”的游戏。

题目描述

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果同时翻转左边的两个硬币,则变为 oooo***oooo。现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

输入格式

两行等长字符串,分别表示初始状态和要达到的目标状态,每行长度小于 10001000

数据保证一定存在至少一种方案可以从初始状态和要达到的目标状态。

输出格式

一个整数,表示最小操作步数。

输入输出样例 #1

输入 #1

1
2
**********
o****o****

输出 #1

1
5

输入输出样例 #2

输入 #2

1
2
*o**o***o***
*o***o**o***

输出 #2

1
1

说明/提示

source:蓝桥杯 2013 省 B 组 H 题

解决方案

在这个情景当中,如果一个硬币连续被翻动两次就相当于没翻,那么如果我们一遇到和目标情况不一样的硬币就把它和它后边一枚硬币翻动的话就能满足无后效性的条件。因为后边翻动的这枚硬币也会在下一次比较中被翻回去,相当于没翻。同时这样一来我们一次遍历就能完成比较,满足最优子结构的条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import sys

def reverse(arr, index):
if arr[index] == '*':
arr[index] = 'o'
else:
arr[index] = '*'

if __name__ == "__main__":
org = list(sys.stdin.readline().strip())
goal = list(sys.stdin.readline().strip())

count, length = 0, len(org)

for i in range(length):
if org[i] != goal[i]:
reverse(org, i)
reverse(org, i+1)
count += 1

print(count)

Bingo!

P8732 [蓝桥杯 2020 国 ABC] 答疑

题目描述

nn 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。老师可以安排答疑的顺序,同学们要依次进入老师办公室答疑。

一位同学答疑的过程如下:

  1. 首先进入办公室,编号为 ii 的同学需要 sis_{i} 毫秒的时间。

  2. 然后同学问问题老师解答,编号为 ii 的同学需要 aia_{i} 毫秒的时间。

  3. 答疑完成后,同学很高兴,会在课程群里面发一条消息,需要的时间可以忽略。

  4. 最后同学收拾东西离开办公室,需要 eie_{i} 毫秒的时间。一般需要 1010 秒、2020 秒或 3030 秒,即 eie_{i} 取值为 100001000020000200003000030000

一位同学离开办公室后,紧接着下一位同学就可以进入办公室了。

答疑从 00 时刻开始。老师想合理的安排答疑的顺序,使得同学们在课程群里面发消息的时刻之和最小。

输入格式

输入第一行包含一个整数 nn,表示同学的数量。

接下来 nn 行, 描述每位同学的时间。其中第 ii 行包含三个整数 si,ai,eis_{i}, a_{i}, e_{i},意义如上所述。

输出格式

输出一个整数,表示同学们在课程群里面发消息的时刻之和最小是多少。

输入输出样例 #1

输入 #1

1
2
3
4
3
10000 10000 10000
20000 50000 20000
30000 20000 30000

输出 #1

1
280000

说明/提示

【样例说明】

按照 1,3,21,3,2 的顺序答疑,发消息的时间分别是 20000,80000,18000020000,80000,180000

【评测用例规模与约定】

对于 30%30 \% 的评测用例, 1n201 \leq n \leq 20

对于 60%60 \% 的评测用例, 1n2001 \leq n \leq 200

对于所有评测用例, 1n1000,1si60000,1ai10000001 \leq n \leq 1000,1 \leq s_{i} \leq 60000,1 \leq a_{i} \leq 1000000, ei{10000,20000,30000}e_{i} \in\{10000,20000,30000\} ,即 eie_{i} 一定是 10000200003000010000 、 20000 、 30000 之一。

蓝桥杯 2020 年国赛 A 组 H 题(B 组 H 题, C 组 J 题)。

解决方案

对于相邻的两个同学ii+1而言,他们发消息的时间的和为:

si+ai+(si+ai+ei+si+1+ai+1)\text{(}s_i+a_i\text{)}+\left( s_i+a_i+e_i+s_{i+1}+a_{i+1} \right)

如果我们将两位同学交换,则变为:

si+1+ai+1+(si+1+ai+1+ei+1+si+ai)\text{(}s_{i+1}+a_{i+1}\text{)}+\left( s_{i+1}+a_{i+1}+e_{i+1}+s_i+a_i \right)

不难发现这次交换是不影响除这两位同学之外的同学们的发消息的时间的,因此满足无后效性。

因此,为了从局部最优解推得全局最优解,我们只需要将前两个式子中的较小的一个放在前边即可。

消元,不难发现,就是比较si,ai,eis_{i}, a_{i}, e_{i}之和与si+1,ai+1,ei+1s_{i+1}, a_{i+1}, e_{i+1}之和。那么我们按此原则对输入排序并计算即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if __name__ == "__main__":
n = int(input())
times = []
for i in range(n):
time = list(map(int, input().split()))
times.append(time)

times.sort(key=lambda x: sum(x))

now, ans = 0, 0
for i in range(n):
now += times[i][0] + times[i][1]
ans += now
now += times[i][2]

print(ans)

Bingo!


贪心算法的应用
http://example.com/2025/04/10/note18/
作者
谢斐
发布于
2025年4月10日
许可协议