日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

动态规划DP题单 AcWing算法基础课 (详解)

發布時間:2025/3/19 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划DP题单 AcWing算法基础课 (详解) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

  • 背包問題
    • 背包問題初始化總結
    • AcWing 2. 01背包問題
    • AcWing 3. 完全背包問題
    • AcWing 4. 多重背包問題
    • AcWing 5. 多重背包問題 II
    • AcWing 9. 分組背包問題
  • 線性DP
    • AcWing 898. 數字三角形
    • AcWing 895. 最長上升子序列
    • AcWing 896. 最長上升子序列 II(貪心)
    • AcWing 897. 最長公共子序列
    • AcWing 902. 最短編輯距離
    • AcWing 899. 編輯距離
  • 區間DP
    • AcWing 282. 石子合并(minminmin
  • 計數類DP
    • AcWing 900. 整數劃分(完全背包求方案數,體積恰好)
  • 數位統計DP
    • AcWing 338. 計數問題
  • 狀態壓縮DP
    • AcWing 291. 蒙德里安的夢想
    • AcWing 91. 最短Hamilton路徑
  • 樹形DP
    • AcWing 285. 沒有上司的舞會
  • 記憶化搜索
    • AcWing 901. 滑雪

背包問題

背包問題初始化總結


總的來說就是 :求方案數,f[0]=1f[0]=1f[0]=1;求最大/最小值 :f[0]=0f[0]=0f[0]=0

AcWing 2. 01背包問題

#include <bits/stdc++.h> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1e3 + 10;int f[N];int main() {IOS;int n, m;cin >> n >> m;for (int i = 0; i < n; i ++ ){int v, w;cin >> v >> w;for (int j = m; j >= v; j -- )f[j] = max(f[j], f[j - v] + w);}cout << f[m] << endl;return 0; }

AcWing 3. 完全背包問題


其實就是把01背包j循環層倒過來寫啊(

#include <bits/stdc++.h> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1e3 + 10;int f[N];int main() {IOS;int n, m;cin >> n >> m;for (int i = 0; i < n; i ++ ){int v, w;cin >> v >> w;for (int j = v; j <= m; j ++ )f[j] = max(f[j], f[j - v] + w);}cout << f[m] << endl;return 0; }

AcWing 4. 多重背包問題


數據范圍非常小,暴力枚舉

#include <bits/stdc++.h> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 110;int f[N];int main() {IOS;int n, m;cin >> n >> m;for (int i = 0; i < n; i ++ ){int v, w, s;cin >> v >> w >> s;for (int j = m; j >= v; j -- )for (int k = 0; k <= s && k * v <= j; k ++ )f[j] = max(f[j], f[j - k * v] + k * w);}cout << f[m] << endl;return 0; }

AcWing 5. 多重背包問題 II

二進制表示法 : 把sss拆成log(s)log(s)log(s)份就可以表示出0 ~sss的所有的數,不再需要s份(s個1)
原來的復雜度是O(N?V?S)O(N * V * S)O(N?V?S),現在是O(N?V?log(S))O(N * V * log(S))O(N?V?log(S))

#include <bits/stdc++.h> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 2e3 + 10;struct Good {int v, w; };int f[N];int main() {IOS;int n, m;cin >> n >> m;vector<Good> goods;for (int i = 0; i < n; i ++ ){int v, w, s;cin >> v >> w >> s;for (int k = 1; k <= s; k *= 2){goods.push_back({k * v, k * w});s -= k;}if (s > 0) goods.push_back({s * v, s * w});}// 得到了我們需要的物品組 :goods,問題轉化為了01背包for (auto good : goods)for (int j = m; j >= good.v; j -- )f[j] = max(f[j], f[j - good.v] + good.w);cout << f[m] << endl;return 0; }

AcWing 9. 分組背包問題

/*for (int i = 0; i < n; i ++ )for (int j = m; j >= 0; j -- )f[j] = max(f[j], f[j - v[0]] + w[0], f[j - v[1]] + w[1], ..., f[j - v[s - 1]] + w[s - 1]);即 f[i][j] 表示只考慮前i組物品,體積至多為j的選法集合每個組內有s + 1種決策即 每個組內選1個(包含0個的情況)與其他組選出來的進行01背包*/#include <bits/stdc++.h> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 110;int v[N], w[N], f[N];int main() {IOS;int n, m;cin >> n >> m;for (int i = 0; i < n; i ++ ){int s;cin >> s;for (int j = 0; j < s; j ++ ) cin >> v[j] >> w[j];for (int j = m; j >= 0; j -- )for (int k = 0; k < s; k ++ )if (j >= v[k]) // 可以選這個f[j] = max(f[j], f[j - v[k]] + w[k]);}cout << f[m] << endl;return 0; }

線性DP

AcWing 898. 數字三角形

  • 路線問題,一般狀態表示可以用坐標
  • f[i][j]f[i][j]f[i][j]表示從底向上走到(i,j)(i, j)(i,j)的所有路線的集合,屬性是maxmaxmax
  • f[i][j]=max(f[i+1][j],f[i+1][j+1])+w[i][j]f[i][j] = max(f[i+1][j],f[i+1][j+1])+w[i][j]f[i][j]=max(f[i+1][j],f[i+1][j+1])+w[i][j]
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 510;int w[N][N], f[N][N];int main() {int n;cin >> n;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= i; j ++ )cin >> w[i][j];for (int i = 1; i <= n; i ++ ) f[n][i] = w[n][i];for (int i = n - 1; i ; i -- )for (int j = 1; j <= i; j ++ )f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + w[i][j];cout << f[1][1] << endl;return 0; }

AcWing 895. 最長上升子序列

  • f[i][j]f[i][j]f[i][j]表示所有以第i個數結尾的上升子序列,屬性是maxmaxmax
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1010;int a[N], f[N];int main() {int n;cin >> n;for (int i = 1; i <= n; i ++ ) cin >> a[i];for (int i = 1; i <= n; i ++ ){f[i] = 1;for (int j = 1; j < i; j ++ )if (a[j] < a[i])f[i] = max(f[i], f[j] + 1);}int res = 0;for (int i = 1; i <= n; i ++ ) res = max(res, f[i]);cout << res << endl;return 0; }

AcWing 896. 最長上升子序列 II(貪心)

  • 相同長度的上升子序列中結尾的數字越小越好,所以我們想到記錄不同長度下同一長度的上升子序列結尾數字的最小值。
  • 會發現,這個序列是單調遞增的。每個插入一個新的數,可以二分找到小于它的數中最大的數,即qj<aiq_j<a_iqj?<ai?,又有qj+1>=aiq_{j+1}>=a_iqj+1?>=ai?,把aia_iai?接在qjq_jqj?后面,再用aia_iai?代替qj+1q_{j+1}qj+1?
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1e5 + 10;int a[N], q[N];int main() {int n;cin >> n;for (int i = 0; i < n; i ++ ) cin >> a[i];int len = 0; // 當前最大長度 / q中元素個數q[0] = -2e9; // 二分的邊界問題,為了保證數組中小于某個數的最大的數一定存在,q[0]是一個哨兵for (int i = 0; i < n; i ++ ){int l = 0, r = len;while (l < r){// 找比a[i]小的中最大的int mid = l + r + 1 >> 1;if (q[mid] < a[i]) l = mid;else r = mid - 1;}len = max(len, r + 1);q[r + 1] = a[i];}cout << len << endl;return 0; }

AcWing 897. 最長公共子序列

  • f[i][j]f[i][j]f[i][j]表示A[1,i],B[1,j]A[1, i],B[1,j]A[1,i],B[1,j]中公共子序列的集合,屬性是公共子序列最長的長度,即求maxmaxmax
  • 根據a[i],b[j]a[i],b[j]a[i],b[j]是否在公共子序列中可以將f[i][j]f[i][j]f[i][j]劃分成00,10,01,1100,10,01,1100,10,01,11,分別對應f[i?1][j?1],f[i][j?1],f[i?1][j],f[i?1][j?1]+1f[i-1][j-1],f[i][j-1],f[i-1][j],f[i-1][j-1]+1f[i?1][j?1],f[i][j?1],f[i?1][j],f[i?1][j?1]+1
  • 其實中間兩種情況用這兩個代替是有重復的,因為f[i?1][j]f[i-1][j]f[i?1][j]本身包含s[j]s[j]s[j]本身在公共子序列中和不在公共子序列中兩種情況,但由于求最大值,所以重復沒關系,且s[j]s[j]s[j]本身不是公共子序列的情況也包含在f[i][j]f[i][j]f[i][j]
  • f[i?1][j?1]f[i-1][j-1]f[i?1][j?1]包含在后兩者的情況中,所以可以省略。
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1010;char a[N], b[N]; int f[N][N];int main() {int n, m;cin >> n >> m >> a + 1 >> b + 1;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){f[i][j] = max(f[i - 1][j], f[i][j - 1]);if (a[i] == b[j])f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);}cout << f[n][m] << endl;return 0; }

AcWing 902. 最短編輯距離

  • f[i][j]f[i][j]f[i][j]表示所有將a[1,i]a[1,i]a[1,i]變成b[1,j]b[1,j]b[1,j]的操作方式,屬性為minminmin
  • “增”“刪”“改”分別對應f[i][j]=min(f[i?1][j]+1,f[i][j?1]+1,f[i?1][j?1]+1/0)f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1,f[i-1][j-1]+1/0)f[i][j]=min(f[i?1][j]+1,f[i][j?1]+1,f[i?1][j?1]+1/0)
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1010;char a[N], b[N]; int f[N][N];int main() {int n, m;cin >> n >> a + 1 >> m >> b + 1;for (int i = 0; i <= n; i ++ ) f[i][0] = i;for (int i = 0; i <= m; i ++ ) f[0][i] = i;for (int i = 1; i <= n; i ++ )for (int j = 1; j <= m; j ++ ){f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1;if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);}cout << f[n][m] << endl;return 0; }

AcWing 899. 編輯距離


注意頭文件。

#include <iostream> #include <algorithm> #include <cstring> #include <string> #include <cmath> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 15, M = 1010;char str[M][N]; int f[N][N];int edit_distance(char a[], char b[]) {int lena = strlen(a + 1), lenb = strlen(b + 1);for (int i = 0; i <= lena; i ++ ) f[i][0] = i;for (int i = 0; i <= lenb; i ++ ) f[0][i] = i;for (int i = 1; i <= lena; i ++ )for (int j = 1; j <= lenb; j ++ ){f[i][j] = min(f[i - 1][j], f[i][j - 1]) + 1;if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);}return f[lena][lenb]; }int main() {int n, m;cin >> n >> m;for (int i = 0; i < n; i ++ ) cin >> str[i] + 1;while (m -- ){char s[N];int limit;cin >> s + 1 >> limit;int res = 0;for (int i = 0; i < n; i ++ )if (edit_distance(s, str[i]) <= limit) res ++ ;cout << res << endl;}return 0; }

區間DP

AcWing 282. 石子合并(minminmin

  • 每次合并相鄰兩堆,因此不是貪心問題。
  • 所有不同的合并順序顯然有(n?1)!(n-1)!(n?1)!(因為每次相鄰兩個)。
  • 相鄰,因此最后必然是將左一堆和右一堆合并的情形,因此聯想到f[i,j]f[i,j]f[i,j]表示將區間[i,j][i,j][i,j]合并成一堆的方案的集合,屬性是minminmin,而分類依據就是兩堆的分界點,由于左右兩堆分別獨立,要總的最小,就分別取最小。這樣f[i,j]f[i,j]f[i,j]一個狀態就可以表示(j?i)!(j-i)!(j?i)!個方案。
  • O(n2?k)==O(n3)O(n^2*k)==O(n^3)O(n2?k)==O(n3)
  • 由于求最小值,因此注意初始化。
  • 要枚舉不同長度的子序列(連續),可以第一層枚舉長度。
  • 狀態計算 :i < j;i = j時,f[i][i] = 0(合并一堆石子代價為0)
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #include <sstream> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 310;int w[N]; int f[N][N];int main() {int n;cin >> n;for (int i = 1; i <= n; i ++ ) cin >> w[i], w[i] += w[i - 1];for (int len = 2; len <= n; len ++ ){for (int i = 1; i + len - 1 <= n; i ++ ){int j = i + len - 1;f[i][j] = 2e9;for (int k = i + 1; k <= j; k ++ ){f[i][j] = min(f[i][j], f[i][k - 1] + f[k][j] + w[j] - w[i - 1]);}}}cout << f[1][n] << endl;return 0; }
  • 記憶化搜索 :

計數類DP

AcWing 900. 整數劃分(完全背包求方案數,體積恰好)

  • 由于不考慮組合的數的順序,因此直接就是完全背包問題了。從1-i中選,體積恰好為j的方案集合。
  • f[i,j]=f[i?1,j]+f[i?1,j?i]+f[i?1,j?2i]+...f[i,j]=f[i-1,j]+f[i-1,j-i]+f[i-1,j-2i]+...f[i,j]=f[i?1,j]+f[i?1,j?i]+f[i?1,j?2i]+...
  • f[i,j?i]=f[i?1,j?i]+f[i?1,j?2i]+...f[i,j-i]=f[i-1,j-i]+f[i-1,j-2i]+...f[i,j?i]=f[i?1,j?i]+f[i?1,j?2i]+...
  • 因此,f[i,j]=f[i,j?i]+f[i?1,j]f[i,j]=f[i,j-i]+f[i-1,j]f[i,j]=f[i,j?i]+f[i?1,j],即,f[j]=f[j?i]+f[j]f[j]=f[j-i]+f[j]f[j]=f[j?i]+f[j],且體積層從小到大循環。
  • 神奇的是雖然這題結果可能很大,需要取模,但不需要long long。
#include <iostream> #include <algorithm> #include <cmath> #include <cstring> #include <string> #include <vector> #include <unordered_map> #include <unordered_set> #include <set> #include <map> #include <stack> #include <queue> #include <deque> #include <ctime> #include <sstream> #define endl '\n' #define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0) #define lowbit(x) (x&-x) using namespace std; const double pi = acos(-1); typedef long long ll; typedef pair<int, int> PII; typedef pair<long, long> PLL;const int N = 1e3 + 10, mod = 1e9 + 7;int f[N];int main() {int n;cin >> n;f[0] = 1;for (int i = 1; i <= n; i ++ )for (int j = i; j <= n; j ++ )f[j] = (f[j] + f[j - i]) % mod;cout << f[n] << endl;return 0; }

數位統計DP

AcWing 338. 計數問題

#include <iostream> #include <algorithm> #include <cmath>using namespace std;int get(int n) { // 求數n的位數int res = 0;while (n) res ++, n /= 10;return res; }int count(int n, int i) { // 求從1到數n中數i出現的次數int res = 0, dgt = get(n);for (int j = 1; j <= dgt; ++ j) {/* p為當前遍歷位次(第j位)的數大小 <10^(右邊的數的位數)>,l為第j位的左邊的數,r為右邊的數,dj為第j位上的數 */int p = pow(10, dgt - j), l = n / p / 10, r = n % p, dj = n / p % 10;// ps:下文的xxx、yyy均只為使讀者眼熟,并不嚴格只是三位數啊~ 然后后續的...就代表省略的位數啦~/* 求要選的數在i的左邊的數小于l的情況: (即視頻中的xxx1yyy中的xxx的選法) --->1)、當i不為0時 xxx : 0...0 ~ l - 1, 即 l * (右邊的數的位數) == l * p 種選法2)、當1位0時 由于不能有前導零 故xxx: 0....1 ~ l - 1, 即 (l-1) * (右邊的數的位數) == (l-1) * p 種選法 */if (i) res += l * p;else res += (l - 1) * p;/* 求要選的數在i的左邊的數等于l的情況:(即視頻中的xxx == l 時)(即視頻中的xxx1yyy中的yyy的選法)--->1)、i > dj時 0種選法2)、i == dj時 yyy : 0...0 ~ r 即 r + 1 種選法3)、i < dj時 yyy : 0...0 ~ 9...9 即 10^(右邊的數的位數) == p 種選法 */if (i == dj) res += r + 1;if (i < dj) res += p;}return res; // 返回結果 }int main() {int a, b;while (cin >> a >> b, a) { // 輸入處理,直到輸入為0停止if (a > b) swap(a, b); // 預處理-->讓a為較小值,b為較大值for (int i = 0; i <= 9; ++ i) cout << count(b, i) - count(a - 1, i) << ' '; // 輸出每一位數字(0 ~ 9)分別在[a,b]中出現的次數<利用前綴和思想:[l,r]的和=s[r] - s[l - 1]>cout << endl; //換行}return 0; // 慣例:結束快樂~ }

狀態壓縮DP

AcWing 291. 蒙德里安的夢想

  • 放方塊時,先放橫著的再放豎著的,總方案數等于只放橫著的方塊的合法方案數。
  • 合法方案數 即 所有剩余位置能用豎著的方塊填滿。從每列來看,每列內部所有連續的空著的方塊是偶數個。
  • 用一個NNN位的二進制數,每一位表示一個物品,0/1表示不同狀態,因此可以用0~2n?12^n-12n?1
    中的數來枚舉全部的狀態。
  • f[i,j]f[i,j]f[i,j]表示已將前i?1i-1i?1列擺好,且從第i?1i-1i?1列伸出到第iii列的狀態是jjj的所有方案,其中jjj是一個二進制數,用來表示哪一行的方塊是橫著放的,其位數和棋盤行數一致。
#include <iostream> #include <vector> #include <cstring>using namespace std;const int N = 12, M = 1 << N; // M = 2^Ntypedef long long LL;LL f[N][M]; // 第一維表示列, 第二維表示所有可能的狀態bool st[M]; // 存儲每種狀態是否有奇數個連續的0, 如果奇數個0則是無效狀態,如果是偶數個零置為trueint n, m;// vector<int> state[M]; // 二維數組記錄合法的狀態 vector<vector<int>> state(M); // 兩種寫法等價 : 二維數組int main() {while (cin >> n >> m, n || m){// 第一部分 : 預處理1// 對于每種狀態,先預處理每列不能有奇數個連續的0for (int i = 0; i < 1 << n; i ++ ) // i -> [0, 2 ^ n - 1]{int cnt = 0; // 記錄連續的0的個數bool isValid = true; // 某種狀態沒有奇數個連續的0則為truefor (int j = 0; j < n; j ++ ) // 遍歷這一列, 從上到下{if (i >> j & 1) // i >> j位運算,表示i(i在此處是一種狀態)的二進制數的第j位,如果這第j位是1,則進入if{if (cnt & 1) // 如果這一位是1,看前面連續的0的個數,如果是奇數(cnt & 1 為真)則不合法{isValid = false;break;}cnt = 0; // 既然該位是1,則到這里0就斷了, cnt清零}else cnt ++ ;}if (cnt & 1) isValid = false; // 最下面的那一段判斷連續的0的個數st[i] = isValid; // 狀態i是否有奇數個連續的0的情況,輸入到st數組中}// 第二部分 : 預處理2// 經過上面每種狀態 連續0的判斷,已經篩掉一些狀態// 下面來看進一步的判斷 : 看第i - 2列滲出來的和第i - 1列伸出去的是否沖突for (int j = 0; j < 1 << n; j ++ ) // 對于第i列的所有狀態{state[j].clear(); // 清空上次操作遺留的狀態,防止影響本次狀態for (int k = 0; k < 1 << n; k ++ ) // 對于第i - 1列的所有狀態{if ((j & k) == 0 && st[j | k]) // 第i - 2列和第i - 1列伸出來的不沖突(不在同一行)// (j & k) == 0 表示j和k沒有一位同時為1,即沒有一列有沖突// 解釋一下st[j | k] :// 已經知道st[]數組表示的是這一列沒有奇數個連續的0的情況// 我們要考慮的是第i - 1列(第i - 1列是這里的主體)中從第i - 2列中橫插過來的和第i - 1列橫插到第i列的// 比如第i - 2列插到第i - 1列的是k = 10101, 第i - 1列插去第i列的是 j = 01000// 那么合在第i - 1列,到底有多少個1呢 ?自然想到的是這兩個操作共同的結果 :兩個狀態或(都是0才是0)。 j | k == 01000 | 10101 == 11101// 這個j | k就是當前第i - 1列的到底有幾個1,即哪幾行是橫著放格子的state[j].push_back(k);// j 表示第i列"真正"可行的狀態, 如果第i - 1列的狀態k和j不沖突則壓入state數組中的第j行// "真正"可行是指 : 既沒有前后兩列伸進伸出的沖突;又沒有奇數個連續的0}}// 第三部分 :dp開始memset(f, 0, sizeof f); // 全部初始化為0,因為是連續讀入,這里是一個清空操作f[0][0] = 1; // 回憶狀態表示的定義 : 前i - 1列都已經擺好,且從第-1列第0列伸出來的狀態為0的方案數// 首先這里沒有第-1列,最少也是第0列。其實,沒有伸出來的,就是沒有橫著擺的,即這個第0列只有豎著擺的這1種狀態// 由于前i-1列已經放好 的 這個定義,所以是從第一行開始而不是第零行for (int i = 1; i <= m; i ++ ) // 遍歷每一列for (int j = 0; j < 1 << n; j ++ ) // 遍歷當前列(第i列)的所有狀態for (auto k : state[j]) // 遍歷第i - 1列的狀態k,如果"真正"可行,就轉移f[i][j] += f[i - 1][k]; // 當前列的方案數就等于之前的第i - 1列所有狀態k的累加// 最后答案是什么呢?// f[m][0]表示前m - 1列都已經處理完,并且第m - 1列沒有伸出來的方案數// 即整個棋盤處理完的方案數cout << f[m][0] << endl;} }

AcWing 91. 最短Hamilton路徑

  • HamiltonHamiltonHamilton路徑 :把每個點經過一次且只經過一次
  • 從0走到n?1n-1n?1 :首先確定走到順序n!n!n!,計算路徑長度 :n!?nn! * nn!?n
  • f[i,j]f[i,j]f[i,j]表示已經走過的所有點是iii,已經走到了jjj點的所有路徑。
  • 根據倒數第二個點是什么進行分類。
#include <iostream> #include <cstring>using namespace std;const int N = 20, M = 1 << N;int n; int f[M][N]; int w[N][N];int main() {cin >> n;for (int i = 0; i < n; i ++ )for (int j = 0; j < n; j ++ )cin >> w[i][j];memset(f, 0x3f, sizeof f);f[1][0] = 0;for (int i = 0; i < 1 << n; i ++ )for (int j = 0; j < n; j ++ )if (i >> j & 1)for (int k = 0; k < n; k ++ )if (i - (1 << j) >> k & 1)f[i][j] = min(f[i][j], f[i - (1 << j)][k] + w[k][j]);cout << f[(1 << n) - 1][n - 1] << endl;//位運算的優先級低于'+'-'所以有必要的情況下要打括號return 0; }

樹形DP

AcWing 285. 沒有上司的舞會

  • 翻譯 :選了某個節點就不能選擇它的父節點和子節點,求最大權值和。
#include <iostream> #include <cstring>using namespace std;const int N = 6010;int n; int e[N], ne[N], h[N], idx; int w[N]; int f[N][2]; bool st[N]; // 是否有父節點void add(int a, int b) {e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; }void dfs(int u) {// f[u][0] = 0;f[u][1] = w[u];for (int i = h[u]; ~i; i = ne[i]) // ~i == i != -1{int j = e[i];dfs(j);// 兩種情況都是 +=f[u][1] += f[j][0];f[u][0] += max(f[j][1], f[j][0]);} }int main() {cin >> n;for (int i = 1; i <= n; i ++ ) cin >> w[i];memset(h, -1, sizeof h); // 不初始化圖見祖宗for (int i = 1; i <= n - 1; i ++ ){int a, b;cin >> a >> b;add(b, a);st[a] = true;}int root = 1;while (st[root]) ++ root;dfs(root);cout << max(f[root][0], f[root][1]) << endl;return 0; }

記憶化搜索

AcWing 901. 滑雪

  • f[i,j]f[i,j]f[i,j]表示所有從(i,j)(i,j)(i,j)開始滑的路徑
#include <iostream> #include <cstring>using namespace std;const int N = 310;int n, m; int h[N][N]; int f[N][N];int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};int dp(int x, int y) {int &v = f[x][y]; // 使用"&",相當于下面每次用到v其實可以換成f[x][y]if (v != -1) return v; // 記憶化搜索v = 1;for (int i = 0; i < 4; i ++ ){int a = x + dx[i], b = y + dy[i];if (a >= 0 && a < n && b >= 0 && b < m && h[a][b] < h[x][y])v = max(v, dp(a, b) + 1);}return v; }int main() {cin >> n >> m;for (int i = 0; i < n; i ++ )for (int j = 0; j < m; j ++ )cin >> h[i][j];memset(f, -1, sizeof f);int res = 0;for (int i = 0; i < n; i ++ )for (int j =0 ; j < m; j ++ )res = max(res, dp(i, j));cout << res << endl;return 0; }

總結

以上是生活随笔為你收集整理的动态规划DP题单 AcWing算法基础课 (详解)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。