1.
从 4 个数列中选择的话总共有 n4 种情况,所以全都判断一遍不可行。不过将它们对半分成 AB 和 CD 再考虑,就可以快速解决了。从两个数列中选择的话只有 n2 种组合,所以可以进行枚举。先从 A、B中取出 a、b 后,为了使总和为 0 则需要从 C、D中取出 c + d = - a - b。因此先将从 C、D 中取数字的 n2 种方法全部都枚举出来,将这些和排好序,这样就可以运用二分搜索。这个算法的复杂度是 O(n2 log n)。
有时候,问题的规模比较大,无法枚举所有元素的组合,但能够枚举一半元素的组合。此时,讲问题拆分两半后分别枚举,再合并它们的结果这一方法往往非常有效。
int n; int A[MAX_N], B[MAX_N], C[MAX_N], D[MAX_N]; int CD[MAX_N * MAX_N]; // C和D中数字的组合方法 void solve() { //枚举 C和D中取出数字的所有方法 for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) CD[i * n + j] = C[i] + D[j]; sort(CD, CD + n * n); long long res = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { int cd = -(A[i] + B[j]); res += upper_bound(CD, CD + n * n, cd) - lower_bound(CD, CD + n * n, cd); } printf("%lld ", res); }
2.
这个问题显然是背包问题。不过这次价值和重量都非常大,相比之下 n 比较小。使用 DP 求解背包问题的复杂度为 O(n W),因此不能用来解决这里的问题。此时我们应该利用 n 比较小的特点来寻找其他办法。
挑选物品的方法总共有 2n 种,所有不能直接枚举,但是像前面一样拆成两半之后再枚举的话,因为每个部分只有 20 个所以是可行的。利用拆成两半后的两部分的价值和重量,我们能求处原先的问题吗?我们把前半部分中的选取方法对应的重量和价值总和记为 w1、v1.这样在后半部分寻找总重 w2 <= W - w1 时使 v2 最大的选取方法就好了。
因此,要思考从枚举得到的(w2,v2)的集合中高效寻找 max{v2 | w2 ≤ W' } 的方法。首先,显然我们可以排除所有 w2[ i ] ≤ w2[ j ] 并且 v2[ i ] ≥ v2[ j ] 的 j。这一点可以按照 w2、v2 的字典序排序后简单做到。此后剩余的元素都满足 w2[ i ] < w2[ j ] <=> v2[ i ] < v2[ j ],要计算 max{ v2 | w2 ≤ W' }的话,只需要寻求满足 w2[ i ] ≤ W' 的最大的 i 就可以了。这可以用二分搜索完成,剩余的元素个数为 M 的话,一次搜索需要 O(log M)的时间。因为 M ≤ 2(n / 2),所以这个算法的复杂度是O(2(n / 2)n)。
typedef long long ll; int n; ll w[MAX_N], v[MAX_N]; ll W; pair<ll, ll> pa[1 << (MAX_N / 2)]; // (重量,价值) void solve() { //枚举前半部分 int n2 = n / 2; for (int i = 0; i < 1 << n2; i++) { ll sw = 0, sv = 0; for (int j = 0; j < n2; j++) if (i >> j & 1) { sw += w[j]; sv += v[j]; } ps[i] = make_pair(sw, sv); } // 去除多余元素 sort(ps, ps + (1 << n2)); int m = 1; for (int i = 1; i < 1 << n2; i++) if (ps[m - 1].second < ps[i].second) ps[m++] = ps[i]; // 枚举后半部分并求解 ll res = 0; for (int i = 0; i < 1 << (n - n2); i++) { ll sw = 0, sv = 0; for (int j = 0; j < n - n2; j++) if (i >> j & 1) { sw += w[n2 + j]; sv += v[n2 + j]; } if (sw <= W) { ll tv = (lower_bound(ps, ps + m, make_pair(W - sw, INF)) - 1)->second; res = max(res, sv + tv); } } printf("%lld ", res); }
枚举方面:
#include<iostream> #include<algorithm> using namespace std; int main() { for (int i = 0; i < 1 << 4; i++) for (int j = 0; j < 4; j++) printf("%d%c", i >> j & 1, j == 3 ? ' ' : ' '); return 0; }