📜  门|门 CS 1997 |问题 17(1)

📅  最后修改于: 2023-12-03 15:42:21.014000             🧑  作者: Mango

'门|门 CS 1997 |问题 17'

简介

该题目来源于 1997 年中国计算机科学竞赛(CSP)省选真题。

题目描述如下:

有一个小镇,镇上有 n 个人,在小镇的中心有一座大门,在门的两边分别是两座交响音乐厅,第 i 个人可以去听第一个音乐厅的演出,也可以去听第二个音乐厅的演出,也可以不去听。

设 A 集合表示选择听第一个音乐厅的人的编号集合,B 集合表示选择听第二个音乐厅的人的编号集合。

每个人达到音乐厅的路程与他的编号有关。假设第 i 个人要去第 k 个音乐厅在他与中心大门间通过的最小编号为 min(i,k),每个人选择的音乐厅会对他到大门的距离造成影响,设这个影响值为 Vo,第 i 个人的影响值总和为 Vi=A(i)*V1+ (1-A(i))*V2 或是 Vi=B(i)*V2+(1-B(i))*V1,其中 A(i) 和 B(i) 分别表示第 i 个人在 A 集合和 B 集合的取值,V1 , V2 是给定的整数,表示不同音乐厅到大门处的距离。求 Vi 最大值以及可以实现 Vi 最大值的 A 集合和 B 集合的元素个数。

解题思路

该题目可以用搜索和动态规划两种方法进行求解。

搜索

搜索的思路比较简单,可以采用回溯法,依次枚举每个人去第一个音乐厅、第二个音乐厅和不去的情况,然后计算影响值,更新最大值。

在每个人计算影响值时,我们可以维护两个数组 A 和 B,分别表示第一个音乐厅和第二个音乐厅选择的人的编号集合,计算影响值时只需要遍历数组即可。

算法复杂度为 O(3^n)。

动态规划

动态规划的思路需要对搜索进行优化,这里只提供了大体思路。

假设当前要考虑第 i 个人,他有三种选择:去第一个音乐厅、去第二个音乐厅、不去。我们可以用 dp[i][0/1/2] 表示当前考虑到第 i 个人,A 集合的元素个数为 0/1/2 时 Vi 的最大值,同理用 dp[i][3/4/5] 表示 B 集合的元素个数为 0/1/2 时 Vi 的最大值。

初始状态为 dp[0][0]=0,表示第 0 个人 A 集合的元素个数为 0 时 Vi 的最大值为 0;dp[0][1]=dp[0][2]=dp[0][3]=dp[0][4]=dp[0][5]=负无穷大,表示 B 集合的元素个数为 0-2 时最大值不存在。

状态转移方程为:dp[i][j]=max(dp[i][j],dp[i-1][j-k]+Vi),其中 k 为当前处理的是 A 集合还是 B 集合,Vi 为第 i 个人在当前选择下的影响值。

最终的答案为 dp[n][0/1/2/3/4/5] 中的最大值,对应的 A 集合和 B 集合的元素个数即为影响值最大时的选择。

算法复杂度为 O(n * 2^3)。

代码实现

由于本题算法可以用搜索和动态规划两种方法求解,这里列出了两种算法的代码实现,注意以下代码仅提供了大体框架,具体实现需要根据不同的语言和编程环境进行调整。

搜索
def dfs(depth, sum_, A, B):
    global max_sum_, A_size_, B_size_

    if depth == n:
        if sum_ > max_sum_:
            max_sum_ = sum_
            A_size_ = len(A)
            B_size_ = len(B)
        return

    dfs(depth + 1, sum_ + calc_v(A, B, depth, 0), A + [depth], B)
    dfs(depth + 1, sum_ + calc_v(A, B, depth, 1), A, B + [depth])
    dfs(depth + 1, sum_ + calc_v(A, B, depth, 2), A, B)

def calc_v(A, B, i, choice):
    if choice == 0:
        return A[i] * v1 + (1 - A[i]) * v2
    elif choice == 1:
        return B[i] * v2 + (1 - B[i]) * v1
    else:
        return 0

n = int(input())
v1, v2 = [int(x) for x in input().split()]
max_sum_ = float('-inf')
A_size_, B_size_ = 0, 0
dfs(0, 0, [], [])
print(max_sum_)
print(A_size_, B_size_)
动态规划
n = int(input())
v1, v2 = [int(x) for x in input().split()]
dp = [[float('-inf')] * 6 for _ in range(n + 1)]
dp[0][0] = 0
for i in range(1, n + 1):
    for j in range(3):
        if j > i:
            break
        dp[i][j] = max(dp[i][j], dp[i - 1][j] + calc_v(j, 0, v1, v2))
        dp[i][j + 3] = max(dp[i][j + 3], dp[i - 1][j + 3] + calc_v(j, 1, v1, v2))
    for j in range(1, 3):
        if j > i:
            break
        dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + calc_v(j, 0, v2, v1))
        dp[i][j + 3] = max(dp[i][j + 3], dp[i - 1][j + 2] + calc_v(j, 1, v2, v1))
print(max(dp[n]))
print(dp[n].index(max(dp[n])) % 3, dp[n].index(max(dp[n])) // 3)
总结

本题考察了搜索和动态规划两种算法,对于动态规划的实现难度比较大,需要学习递推的思想和状态转移方程的设计。同时,本题也锻炼了我们对于问题的分析、建模和代码实现能力,对于提高编程综合素质具有一定的意义。