DP[1]

Dynamic Programming II

INDEX

LIS

LCS

Bitmask DP

 

LIS

最長遞增子序列

1. 遞增

2. 子序列

3. DP 做法

遞增

Increasing

分為兩種:
3
3
5
6
7
8
8
9
array
0
1
2
3
4
5
6
7
index
嚴格遞增 (後項 > 前項)
非嚴格遞增 (後項 \geq 前項)
它非\ 嚴格遞增 但\ 非嚴格遞增

子序列

Subsequence

你從一個數列 A 中,隨便抓幾個數字出來
3
3
5
6
7
8
8
9
A
讓他們照著在原數列內的前後關係排列,變成數列 B
B就是A的子序列
0
1
2
3
index
3
5
7
9
B
0
1
2
3
4
5
6
7
index

最長遞增子序列

LIS (Longest Increasing Subsequence)

它是子序列又遞增(至於哪一種遞增,看題目要求)
給定一個數列,求LIS長度(嚴格遞增)
9
1
3
7
5
6
20
21
array
0
1
2
3
4
5
6
7
index
1
3
5
6
20
21
Ans:\ 6

最長遞增子序列

LIS (Longest Increasing Subsequence)

怎麼用 dp 的想法實作?
要讓LIS盡可能長,我們會發現,子序列結尾越小,後面就能塞越多數字
會影響後面能不能塞數字的只有結尾數字
那我們開個dp陣列
把每個元素依照順序丟進去dp
dp陣列存的是在特定長度下LIS,結尾最小的

最長遞增子序列

LIS (Longest Increasing Subsequence)

9
1
3
7
5
6
20
21
array
0
1
2
3
4
5
6
7
index
dp陣列存的是在特定長度下LIS,結尾最小的
...
9
1
1
3
5
6
20
21
1
3
5
6
20
0
1
7
6

最長遞增子序列

LIS (Longest Increasing Subsequence)

怎麼dp
開一個陣列
把元素一個一個丟進陣列
如果該元素比結尾元素大,陣列大小+1,該元素丟到最後面
如果比較小,找到第一個比該元素大的元素,替換成該元素
找"第一個更大的元素",可以二分搜,STL\ 中的\ lower\_bound\ 可以拿出來用

最長遞增子序列

Code

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> number(n);
    
    for (int i = 0; i < n; i++) {
        cin >> number[i];
    }
    
    vector<int> dp;
    dp.push_back(number[0]);
    
    for (int i = 1; i < n; i++) {
        if (number[i] > dp[dp.size() - 1]) {
            dp.push_back(number[i]);
        }
        else {
            auto replace = lower_bound(dp.begin(), dp.end(), number[i]);
            *replace = number[i];
        }
    }
    
    cout << dp.size() << '\n';
}
總複雜度\ O(nlogn)

題目

補充,如果要回溯LIS

記錄好前面的數字就可以回溯了

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> number(n);
    
    for (int i = 0; i < n; i++) {
        cin >> number[i];
    }
    
    vector<int> dp;
    dp.push_back(number[0]);
    
    // 用來記錄每個元素前面值的索引
    vector<int> prev(n, -1);
    // 用來記錄 dp 中每個元素的索引
    vector<int> position(n, -1);
    position[0] = 0;
    
    for (int i = 1; i < n; i++) {
        int n = number[i];
        
        if (n > dp.back()) {
            prev[i] = position[dp.size() - 1];  // 更新前元素索引
            dp.push_back(n);
            position[dp.size() - 1] = i;
        } else {
            auto it = lower_bound(dp.begin(), dp.end(), n);
            int idx = it - dp.begin();
            dp[idx] = n;
            position[idx] = i;
            if (idx > 0) {
                prev[i] = position[idx - 1];  // 更新前元素索引
            }
        }
    }
    
    // 從 dp 中最後一個元素開始回溯 LIS
    int idx = position[dp.size() - 1];
    vector<int> lis;
    
    while (idx != -1) {
        lis.push_back(number[idx]);
        idx = prev[idx];
    }
    
    // 反轉 LIS 以便從小到大順序
    reverse(lis.begin(), lis.end());
    
    // 輸出 LIS 的長度和內容
    cout << dp.size() << '\n';
    for (int i = 0; i < lis.size(); i++) {
        cout << lis[i] << " ";
    }
    cout << '\n';
}

LCS

最長共同子序列

1. 共同子序列

2. DP 做法

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
array
0
1
2
3
4
5
6
index
b
d
c
a
b
a
array
0
1
2
3
4
5
index
兩個陣列都有的子序列中最長的

LCS = {b,c,b,a} , {b,c,a,b}

最長共同子序列

LCS (Longest Common Subsequence)

求LCS長度,dp做法
dp[i][j] 代表的是各考量A陣列的前i項與B陣列的前j項時,LCS長度
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
初始化:i, j其中一者為0時,不會有共同子序列,故長度為0

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1
0

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1
0
1

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1
0
1
1

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1
0
1
1
1

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1
0
1
1
1
2

最長共同子序列

LCS (Longest Common Subsequence)

對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
0
1
1
1
1
1
1
0
1
1
1
2
2

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
0
1
1
1
2
2
對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
2

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
0
1
1
1
2
2
對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
2
0

最長共同子序列

LCS (Longest Common Subsequence)

a
b
c
b
d
a
b
b
d
c
a
b
a
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
0
1
1
1
2
2
對於dp[i][j],當A[i]=B[j]時 = dp[i-1][j-1]+1
當A[i]!=B[j]時 = max(dp[i-1][j],dp[i][j-1])
2
0
1
2
2
2
2
2
1
1
2
2
2
3
3
1
2
2
3
3
3
4
1
2
2
3
3
4
4
Ans: 4

最長共同子序列

Code

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    string a,b;
    cin >> a >> b;
    
    vector<vector<int>> dp(n+1, vector<int>(m+1));
    
    for (int i=0;i<=n;i++) {
        dp[i][0] = 0;
    }
    
    for (int i=0;i<=m;i++) {
        dp[0][i] = 0;
    }
    
    for (int i=0;i<n;i++) {
        for (int j=0;j<m;j++) {
            if (a[i]==b[j]) {
                dp[i+1][j+1] = dp[i][j] + 1;  
            }
            else {
                dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);  
            }
        }
    }
    
    cout << dp[n][m];
}
總複雜度\ O(nm)

題目

最長共同子序列 + 回溯 LCS

Code

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    string a, b;
    cin >> a >> b;
    
    vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
    
    vector<vector<int>> prev(n+1, vector<int>(m+1, 0)); 
    
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (a[i-1] == b[j-1]) {
                dp[i][j] = dp[i-1][j-1] + 1;
                prev[i][j] = 0; 
            } else {
                if (dp[i-1][j] > dp[i][j-1]) {
                    dp[i][j] = dp[i-1][j];
                    prev[i][j] = 2; 
                } else {
                    dp[i][j] = dp[i][j-1];
                    prev[i][j] = 1; 
                }
            }
        }
    }
    
    cout << dp[n][m] << '\n';
    
    string lcs = "";
    int i = n, j = m;
    
    while (i > 0 && j > 0) {
        if (prev[i][j] == 0) {
            lcs = a[i-1] + lcs;
            i--;
            j--;
        } else if (prev[i][j] == 1) {
            j--;
        } else if (prev[i][j] == 2) { 
            i--;
        }
    }
    
    cout << lcs << '\n';
    
    return 0;
}
總複雜度\ O(nm)

Bitmask DP

位元遮罩

1. Bitmask

2. DP 應用

位元遮罩

Bitmask

給你一個數列,請你枚舉它的子集合
0
1
2
3
4
5
6
7
array
對於每個東西取/不取
取 = 1,不取 = 0
0
0
0
0
0
0
0
0
1
0
1
0
0
0
0
1
1
1
1
1
1
1
1
1
全不取
全取
取0、2、7

Bitmask

代表不同種集合

位元遮罩

Bitmask

給你一個數列,請你枚舉它的子集合
0
1
2
3
4
5
6
7
array
0
0
0
0
0
0
0
0
1
0
1
0
0
0
0
1
1
1
1
1
1
1
1
1
0 =
255 =
161 =
因為可以二進制可以轉十進制,所以 Bitmask 可以用一個整數來表達
所以你只要從0到255都跑過,你就可以枚舉完所有的集合了

位元遮罩

Bitmask

#include <bits/stdc++.h>
using namespace std;

int main() {
   for (int i = 0; i < (1 << 8); i++) { 
   		// 從全是 0 跑到 全是 1,也就是跑到 (1 << 8) - 1
        for (int j = 0; j < 8; j++) {
            if (i & (1 << j)) { 
            	// 如果 j 那格有被取到(1),& 的結果就會 >= 1
                // 如果沒有被取到(0),& 的結果就是 0
                cout << j << ' ';
            }
        }
        cout << '\n'; 
    }
}
枚舉0到7的所有子集合

位元遮罩 DP

Bitmask DP

怎麼用在 DP?

題目

有 n 個人想要搭乘電梯到達一棟建築的頂端。每個人都有一個特定的體重,
而電梯有一個最大承重限制。你需要計算最少需要多少次電梯行程
,才能將所有人都送到頂端。

位元遮罩 DP

Bitmask DP

有 n 個人想要搭乘電梯到達一棟建築的頂端。每個人都有一個特定的體重,
而電梯有一個最大承重限制。你需要計算最少需要多少次電梯行程
,才能將所有人都送到頂端。
顯而易見的一個做法:
把所有人都塞上電梯,如果電梯超重了再加開下一趟電梯
但有個問題,我們要怎麼在dp時描述誰搭過,誰沒搭過電梯
Bitmask!

位元遮罩 DP

Bitmask DP

有 n 個人想要搭乘電梯到達一棟建築的頂端。每個人都有一個特定的體重,
而電梯有一個最大承重限制。你需要計算最少需要多少次電梯行程
,才能將所有人都送到頂端。
解題思路:
用 Bitmask\ DP 來表示每個人是否已經搭過電梯,記做狀態 i
dp[i] 表示當有狀態 i(即某些人已經搭過電梯)的情況下,所需的最少電梯次數。
last[i] 表示在狀態 i 下,最後一次搭乘電梯所剩下的重量

位元遮罩 DP

Bitmask DP

轉移 :

如果他進電梯時會超重,即: last[prev] + W[j] > 重量限制

則加開一台電梯載他,last[i] = w[j],dp[i] = dp[prev] + 1

如果他進電梯時不會超重,

就把它塞進電梯,last[i] = last[prev] + w[j],dp[i] = dp[prev]

對於每個狀態,考慮有搭電梯的每個人,如果他是最後進入電梯的,
答案會更好(次數更小) / 次數一樣但剩餘重量更小

註: prev = i & ~(1 << j),即 i 中但 j 搭電梯前的狀態

#include<bits/stdc++.h>
#define int long long
using namespace std;
 
signed main() {
    int n, x;
    cin >> n >> x;
    int w[n];
    
    for (int i=0;i<n;i++) {
        cin >> w[i];
    }
    
    int dp[(1<<n)];
    int last[(1<<n)];
    
    dp[0] = 1;
    last[0] = 0;
    
    for (int i=1;i<(1<<n);i++) {
        dp[i] = 1e9;
        last[i] = 1e9;
        for (int j=0;j<n;j++) {
            if (i & (1<<j)) {
                int prev = i ^ (1<<j);
                if (last[prev] + w[j] > x) { 
                    if (dp[prev] + 1 < dp[i] || (dp[prev] + 1 == dp[i] && w[j] < last[i])) {
                        dp[i] = dp[prev] + 1;
                        last[i] = w[j]; 
                    }
                } else {
                    if (dp[prev] < dp[i] || (dp[prev] == dp[i] && last[prev] + w[j] < last[i])) {
                        dp[i] = dp[prev];
                        last[i] = last[prev] + w[j];
                    }
                }
            }
        }
    }
    
    
    cout << dp[(1<<n)-1] << '\n';
}

AC Code

其他題目

掰掰

Dynamic Programming II

By wen Ian

Dynamic Programming II

基礎DP第二堂,上一堂連結請見 source 的那份簡報

  • 258