目录

  • 背包问题
    • 背包问题初始化总结
    • 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算法基础课 (详解)相关推荐

  1. 【目标检测】单阶段算法--YOLOv4详解

    论文题目 :<YOLOv4: Optimal Speed and Accuracy of Object Detection> 论文地址:https://arxiv.org/pdf/2004 ...

  2. 【目标检测】单阶段算法--YOLOv2详解

    论文题目:<YOLO9000: Better, Faster, Stronger> 论文地址:https://arxiv.org/pdf/1612.08242.pdf 一文读懂YOLOv1 ...

  3. 【目标检测】单阶段算法--YOLOv3详解

    论文题目:<YOLOv3: An Incremental Improvement > 论文地址:https://pjreddie.com/media/files/papers/YOLOv3 ...

  4. 【目标检测】单阶段算法--YOLOv1详解

    论文题目:<You Only Look Once:Unified, Real-Time Object Detection> 论文地址:https://arxiv.org/pdf/1506. ...

  5. 算法与数据结构模版(AcWing算法基础课笔记,持续更新中)

    AcWing算法基础课笔记 文章目录 AcWing算法基础课笔记 第一章 基础算法 1. 排序 快速排序: 归并排序: 2. 二分 整数二分 浮点数二分 3. 高精度 高精度加法 高精度减法 高精度乘 ...

  6. 背包四讲 (AcWing算法基础课笔记整理)

    背包四讲 背包问题(Knapsack problem)是一种组合优化的NP完全问题.问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高 ...

  7. CRF(条件随机场)与Viterbi(维特比)算法原理详解

    摘自:https://mp.weixin.qq.com/s/GXbFxlExDtjtQe-OPwfokA https://www.cnblogs.com/zhibei/p/9391014.html C ...

  8. JavaScript数据结构与算法——链表详解(下)

    在JavaScript数据结构与算法--链表详解(上)中,我们探讨了一下链表的定义.实现原理以及单链表的实现.接下来我们进一步了解一下链表的其他内容. 1.双向链表 双向链表实现原理图: 与单向链表不 ...

  9. [转]数据结构KMP算法配图详解(超详细)

    KMP算法配图详解 前言 KMP算法是我们数据结构串中最难也是最重要的算法.难是因为KMP算法的代码很优美简洁干练,但里面包含着非常深的思维.真正理解代码的人可以说对KMP算法的了解已经相当深入了.而 ...

最新文章

  1. jquery动态添加列表后样式失效解决方式
  2. ABAP--一个不错的函数模块的文档生成程序
  3. linux查看修改环境变量日志,linux查看和修改PATH环境变量的方法
  4. Stacktraces告诉了事实。 但事实并非如此。
  5. html如何做卷展菜单,3DMax“对象属性”卷展栏如何操作?有啥秘诀呢?
  6. 《前端工程化体系设计与实践》-笔记
  7. bubbo调用Failed to invoke remote method异常解决
  8. 认识Spring以及环境的配置
  9. c语言知5个学生的4门成绩,用C 语言数组解题 :已知5个学生的4门课的成绩,要求求出全部学生的门成绩并分别求出每门课的平均成绩...
  10. Eclipse常用快捷键大全
  11. Python基础——min/max与np.argmin/np.argmax
  12. 【三维路径规划】基于matlab A_star算法无人机三维路径规划(起终点障碍物可设置)【含Matlab源码 1321期】
  13. 第 7 章 Neutron - 077 - 配置 linux-bridge mechanism driver
  14. 均线策略python代码_Python版商品期货多品种均线策略
  15. python合并大量ts文件_Python3 根据m3u8下载视频,批量下载ts文件并且合并
  16. java 车牌模糊_免费模糊车牌照片处理软件
  17. 8700K + z370 安装黑苹果 Mojave
  18. 计算机信息安全 概述
  19. 2020.10.25丨全长转录组结构分析之可变剪切、lncRNA预测
  20. 【点云配准算法】【NDT】

热门文章

  1. 【基础】ABAP不同变量类型之间数值大小比较
  2. 感恩八年 — 致CSDN (感谢有你)
  3. SAP发票校验前收货后是否能更改物料价格
  4. TIME_CHECK_PLAUSIBILITY
  5. mysql 子查询概念_聊聊MySQL的子查询
  6. python装饰器有几种_python几种装饰器的用法
  7. 华为申请鸿蒙系统邮箱,华为鸿蒙系统
  8. 区间比较_Simulink(离散PIDamp;区间检测模块)+AURIX功能安全板过流比较电路+电机结构应用与工作原理学习网站...
  9. CTF-不一样的凯撒密码
  10. Python字典推导式将cookie字符串转化为字典