1. 程式人生 > 其它 >動態規劃-計數dp-2262. 字串的總引力

動態規劃-計數dp-2262. 字串的總引力


title: 活動選擇問題
date: 2022-05-09 14:40:50
tags: 演算法 貪心策略 活動選擇問題

活動選擇問題

無權值

問題背景

有一個會場,需要安排幾場活動

  • 公司年會:10:00 - 19:00
  • 婚禮宴請:11:00- 14:00
  • 生日聚會:12:00 - 17:00
  • 學術研究:14:00 - 16:00

問 如果安排這個會場能讓活動儘可能的多的進行?

有下面這幾個活動和佔比時間

選擇出租的時間不能有衝突

問題研究,策略選擇

​ 這時候我們採用貪心策略:

  • 策略一:最短活動優先

  • 策略二:最早開始的活動優先

  • 策略三:最早結束活動優先

選擇最早結束的活動,可以給後面的活動留出更大的弓箭

  1. 證明該策略是正確的(替換法)

我們可以通過替換的規則讓 某個最優解替換為 貪心所得的最優解,在替換的時候,如果活動相同的話,直接替換即可,如果不相同的話,因為貪心所得的是這個活動的最先完成 的活動,所有會給後面的活動留出的活動空間更大不會影響後面活動的替換

程式碼實現

輸入:活動集合S={a1, a2, a3 .. an},每個活動ai的起始時間si 結束時間 fi

輸出:不衝突的最大子集S‘

先把活動結束時間由小到大排個序

先給陣列S賦值 a1(因為這裡a1是最早活動的結束時間)

令K = 1

for i 迴圈 從2 到最後

如果 ai 的起始時間 小於 ak的結束時間的話,就把ai新增到S’中, 把 i賦值給k

最後返回 S

import random

# 活動選擇問題
# 我們是用元組來初始化資料(開始時間, 結束時間)

# 按照活動結束時間從小到大排序


def activity_sort(data):
    # data中的資料都是按照活動結束時間從小到大排序的,也就是說a1是結束時間最早的,依次排序
    S = []
    S.append(data[0])
    print("事件1")

    k = 0
    for i in range(1, len(data)):
        # 如果第i個時間的開始時間 小於第k個時間的結束時間的話,則表明這個時間他衝突,跳過
        if data[i][0] >= data[k][1]:
            print(f"事件{i+1}")
            k = i
    return S


# 快速排序
def quick_sort(data, start, end):

    if start > end:
        return 0

    # 先獲取一個隨機數當做主元
    prime_index = random.randint(start, end)

    # 把這個隨機數放到最後方便計算
    data[end], data[prime_index] = data[prime_index], data[end]
    
    # 尋找該主元的正確的下標
    # 下標i以後的為小於主元元素的
    # 下標j以後i以前的表示大於該元素的

    i = start-1
    j = start

    while j < end:
        # 如果j 元素比主元小的話,就讓i+1元素和j元素互換位置,並讓i+=1 j+=1 
        # 因為:下標i以後的為小於主元元素的,下標j以後i以前的表示大於該元素的
        if data[j][1] <= data[end][1]:
            data[i+1], data[j] = data[j], data[i+1]
            i += 1
            j += 1
        else:
            j += 1
    
    # 最後吧主元放到對應的位置
    data[i+1], data[end] = data[end], data[i+1]

    # 進行遞迴呼叫
    quick_sort(data, start, i)
    quick_sort(data, i+2, end)

data = [(1,4,1),(0,6,4), (3,5,6),(2,14,8), (3,9,3), (4,7,7),(5,9,12), (6,10,2), (8,11,9),(8,12,11)]
quick_sort(data, 0, 9)
activity_sort(data)

有權值

上述問題我們預設為每個事件的收入都是 1, 所以我們關注一共有多少個時間的發生:

所以相比於上面的問題我們多了一個權重:

這時候如果我們在向上面的方式來進行選擇問題的話,這時候可能就會得不償失

如果按照上述方法進行第一個問題的話 我們選擇的權值和為2, 但是實際上我們想要的權值和為 10000

問題分析

這時候我們需要進行動態規劃 + 貪心演算法來求解這個問題, 選擇a6 和 a5的時候我們可以根據前面的最優問題來進行求解

問題預處理

這裡我們需要定義一個

p陣列 p[i] 表示在a[i]開始前最後結束的活動,

p陣列目的:為了防止活動衝突

D[i]陣列:集合{a1, a2, a3.... an}中不衝突活動的最大權值和

遞推關係建立

當我們在ai的時候,

  1. 選擇ai 那麼 D[i] = Wai + D[p[i]]
  2. 不選擇ai D[i] = D[i-1]
  3. 遞推公式:D[i] = max{D[pi] + w, D[i-1]}

計算過程

P陣列表示 ai 發生之前的最晚發生的事件

D陣列表示權值的最大

Rec 1表示選擇這個活動, 0表示不選擇這個活動

程式碼實現

import random

# 活動選擇問題
# 我們是用元組來初始化資料(開始時間, 結束時間)

# 按照活動結束時間從小到大排序


def activity_sort(data, D, Rec, p):

    # 進行D陣列和Rec陣列
    # D陣列:權值選擇問題
    # Rec陣列:是否選擇改事件
    # D[i] 的依據是 max{D[i-1], D[p1] + wi}
    for num_index in range(len(data)):
        if D[num_index] > (D[p[num_index]] + data[num_index][2]):
            D[num_index+1] = D[num_index]
            Rec[num_index] = 0
        else:
            D[num_index+1] = D[p[num_index]] + data[num_index][2]
            Rec[num_index] = 1
    
    # 陣列追蹤
    print(f"事件總權重為{D[len(D)-1]}")
    i = len(Rec)-1
    while i >= 0:
        if Rec[i] == 1:
            print(f"選擇事件{i+1},權重{data[i][2]}")
            i = p[i]-1
            continue
        i -= 1
        

# 快速排序
def quick_sort(data, start, end):

    if start > end:
        return 0

    # 先獲取一個隨機數當做主元
    prime_index = random.randint(start, end)

    # 把這個隨機數放到最後方便計算
    data[end], data[prime_index] = data[prime_index], data[end]
    
    # 尋找該主元的正確的下標
    # 下標i以後的為小於主元元素的
    # 下標j以後i以前的表示大於該元素的

    i = start-1
    j = start

    while j < end:
        # 如果j 元素比主元小的話,就讓i+1元素和j元素互換位置,並讓i+=1 j+=1 
        # 因為:下標i以後的為小於主元元素的,下標j以後i以前的表示大於該元素的
        if data[j][1] <= data[end][1]:
            data[i+1], data[j] = data[j], data[i+1]
            i += 1
            j += 1
        else:
            j += 1
    
    # 最後吧主元放到對應的位置
    data[i+1], data[end] = data[end], data[i+1]

    # 進行遞迴呼叫
    quick_sort(data, start, i)
    quick_sort(data, i+2, end)

# 初始化p陣列
def init_p(data):
    p = [0 for i in range(len(data))]

    for i in range(len(data)):
        # k表示第i個時間的起始時間
        start_time = data[i][0]
        start = 0
        end = len(data)-1
        flag = 0
        while(start < end):
            mid = start + int((end - start)/2)
            # 如果中間事件的結束時間小於要查詢的事件
            if data[mid][1] == start_time:
                flag = mid
                break
            elif data[mid][1] > start_time:
                end = mid-1
            else:
                start = mid + 1
        if flag != 0:
            p[i] = flag+1
        elif data[start][1] > start_time:
            p[i] = start
        else:
            p[i] = start+1
    return p



data = [(1,4,1),(0,6,4), (3,5,6),(2,14,8), (3,9,3), (4,7,7),(5,9,12), (6,10,2), (8,11,9),(8,12,11)]
quick_sort(data, 0, 9)

D = [0 for i in range(len(data) + 1)]
Rec = [0 for i in range(len(data))]
# 初始化P陣列 p[i] 表示事件i開始前最晚結束的事件
p = init_p(data)

activity_sort(data, D, Rec, p)
print(D)
print(Rec)
print(p)