for (int i = 1; i <= n; i ++) for (int j = m; j >= v[i]; j --) // f[i][j] = f[i - 1][j]; // if (j >= v[i]) j 直接从 v[i] 开始,则无需判断 f[j] = max(f[j], f[j - v[i]] + w[i]); // 将 j 倒序计算,则可以保证 j - v[i] 为 i - 1 层的值
cout << f[m] << endl;
完全背包
完全背包的状态计算:
类比于01背包将当前情况分为选(1)或不选(0),在完全背包中可以按照选多少个物品分类
对于各个集合中状态的表示,设当前物品选了 k 个,即有状态转移方程: f[i - 1][j - k * v[i]] + k * w[i]
将每个情况取最大值,即可求出当前情况的价值最大值
朴素解:
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int n, m; int v[N + 10], w[N + 10]; int f[N + 10][N + 10];
cin >> n >> m; for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++) for (int j = 1; j <= m; j ++) for (int k = 0; k * v[i] <= j; k ++) // 枚举物品数量,保证总体积不超过 j f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]); cout << f[n][m] << endl;
for (int i = 1; i <=n; i ++) for (int j = v[i]; j <= m; j ++) f[j] = max(f[j], f[j - v[i]] + w[i]);
到此为止,我们已经将完全背包的时间复杂度优化到了 O(n)
多重背包
对于每个物品都有个数限制 s[i],只需枚举物品个数 k 并使 k 满足 k <= s[i] && k * v[i] <= j
朴素版本:
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14
int n, m; int v[N + 10], w[N + 10], s[N + 10]; int f[N + 10][N + 10]; cin >> n >> m; for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i]; for (int i = 1; i <= n; i ++) for (int j = 1; j <= m; j ++) for (int k = 0; k <= s[i] && k * v[i] <= j; k ++) // 保证总体积不超过 j 的同时使 k 不超过限制个数 f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]); cout << f[n][m] << endl;
constint N = 25000; // 一共有 1000 项,每项最多拆分成 log s项 constint M = 2010;
int n, m; int v[N], w[N]; int f[N];
cin >> n >> m;
int cnt = 0; for (int i = 1; i <= n; i ++) { int a, b, s; cin >> a >> b >> s; for (int k = 1; k <= s; k *= 2) // 从2^0 开始枚举 2 的次幂 { s -= k; v[++ cnt] = a * k; w[cnt] = b * k; } if (s > 0) { v[++ cnt] = a * s; w[cnt] = b * s; } } n = cnt; // 二进制拆分读入 for (int i = 1; i <= n; i ++) for(int j = m; j >= v[i]; j --) f[j] = max(f[j], f[j - v[i]] + w[i]);
intmain() { cin >> n >> m; for (int i = 1; i <= n; i ++) { cin >> s[i]; for (int j = 0; j < s[i]; j ++) cin >> v[i][j] >> w[i][j]; } for (int i = 1; i <= n; i ++) for (int j = m; j >= 0; j --) for (int k = 0; k < s[i]; k ++) if (v[i][k] <= j) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]); cout << f[m] << endl; return0; }
int n; // n 表示元素个数 int a[N], q[N]; // a[] 用于存储数列 // p[] 用于维护所有不同长度的最长上升子序列结尾元素的最小值
cin >> n; for (int i = 0; i < n; i++) cin >> a[i];
int len = 0; // 存储当前枚举到的最长上升子序列的长度,即 q 数组中的元素个数 q[0] = -2e9; // 初始化边界 for (int i = 0; i < n; i ++) { // 二分查找不大于 a[i] 的最大值在 p 中的元素位置 int l = 0, r = len; while (l < r) { int mid = l + r + 1 >> 1; if (q[mid] < a[i]) l = mid; else r = mid - 1; } // r 返回不大于 a[i] 的最大值的下标(即长度) len = max(len, r + 1); // 将 a[i] 连接在不大于 a[i] 的最大值的最长上升子序列的后面,表示长度,即 r + 1 q[r + 1] = a[i]; }
cin >> n; for (int i = 1; i <= n; i ++) cin >> s[i]; // input for (int i = 2; i <= n; i ++) s[i] += s[i - 1]; // 处理前缀和
for (int len = 2; len <= n; len ++) // 枚举待合并的区间长度 for (int i = 1; i + len - 1 <= n; i ++) // 枚举区间位置 { int l = i, r = i + len - 1; // 取出起始位置 f[l][r] = INF; // 初始化一个最大值,循环找出最小值 for (int k = l; k < r; k ++) f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]); // 按合并区间的分界线分类枚举 } cout << f[1][n] << endl;
值得注意的是,题目要求输出一个正整数 n 的所有拆分方案,而以上的状态转移方程仅限于正整数 i 拆分为 j 个数,因此对于所有方案的计算,只需:
cpp
1 2
for (int i = 1; i <= n; i ++) ans += f[n][i];
正解
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
int n; int f[N][N]; int res;
cin >> n; f[0][0] = 1;
for (int i = 1; i <= n; i ++) for (int j = 1; j <= i; j ++) f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % MOD; for (int i = 1; i <= n; i ++) res = (res + f[n][i]) % MOD;
intdp(int x, int y) { int &v = f[x][y];//&v引用符号,每次写v都等同于写f[x][y] if(v != -1) return v; //v被算过,直接返回v,否则计算v v = 1; //最差v也可以走当前的格子 for(int i = 0; i < 4; i ++) { int a = x + dx[i], b = y + dy[i]; if(a >= 1 && a <= n && b >= 1 && b <= m && h[a][b] < h[x][y]) v = max(v, dp(a, b) + 1); }
return v; }
intmain() { cin >> n >> m; for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) cin >> h[i][j];
memset(f, - 1, sizeof(f));//常用,表示每一个点都没有被算过
int res = 0; for(int i = 1;i <= n; i ++) for(int j = 1; j <= m; j ++) res = max(res, dp(i, j));