买瓜 
小蓝正在一个瓜摊上买瓜。
瓜摊上共有 个瓜,每个瓜的重量为 。
小蓝刀功了得,他可以把任何瓜劈成完全等重的两份,不过每个瓜只能劈一刀。
小蓝希望买到的瓜的重量的和恰好为 。
请问小蓝至少要劈多少个瓜才能买到重量恰好为 的瓜。
如果无论怎样小蓝都无法得到总重恰好为 的瓜,请输出
−1。对于 20% 的评测用例, ; 对于 60% 的评测用例, ; 对于所有评测用例,,, 。
一份能过洛谷的折半搜索代码,自己实现了哈希表(若 sum 爆 int ,可考虑将 sum 变量换成 LL )
c++
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 15000010; // 3^(n/2) <= 143489073 
int h[N], e[N], ne[N], idx = 1;
int v[N];
const int MOD = 9998777;
void insert(int key, int val)
{
    int id = key % MOD;
    v[idx] = val;
    e[idx] = key;
    ne[idx] = h[id];
    h[id] = idx ++;
}
int find(int x)
{
    if (x < 0) return -1;
    for (int i = h[x % MOD]; i; i = ne[i])
    {
        if (e[i] == x) return i;
    }
    return -1;
}
int n, m;
int a[35];
int res;
void dfs(int l, int r, int sum, int cnt)
{
    if (sum > m) return;
    if (l > r)
    {
        int t = find(sum);
        if (t == -1) insert(sum, cnt);
        else v[t] = min(v[t], cnt);
    }
    else
    {
        dfs(l + 1, r, sum, cnt);
        dfs(l + 1, r, sum + a[l], cnt);
        dfs(l + 1, r, sum + a[l] / 2, cnt + 1);
    }
}
void dfs2(int l, int r, int sum, int cnt)
{
    if (sum > m || cnt > res) return;
    if (l > r)
    {
        int t = find(m - sum);
        if (t != -1) res = min(res, v[t] + cnt);
        return;
    }
    else
    {
        dfs2(l + 1, r, sum, cnt);
        dfs2(l + 1, r, sum + a[l], cnt);
        dfs2(l + 1, r, sum + a[l] / 2, cnt + 1);
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    m *= 2;
    for (int i = 1; i <= n; i++) 
    {
        scanf("%d", &a[i]);
        a[i] *= 2;
    }
    
    res = 0x3f3f3f3f;
    sort(a + 1, a + 1 + n, [](int x, int y)->bool {return x > y;});
    dfs(1, n / 2, 0, 0);
    dfs2(n / 2 + 1, n, 0, 0);
    
    if (res == 0x3f3f3f3f) puts("-1");
    else printf("%d\n", res);
    return 0;
}[Leetcode956最高的广告牌][https://leetcode.cn/problems/tallest-billboard/description/] 
题意是给定一个数组,选两个子集出来作为支架的长度,要保证左右支架长度相等,求最大长度。
数据范围是 , ,从数据范围中可以得到提示:总和这么小,那么 很可能作为一个需要利用的信息。本题思考的核心是左右支架高度的差值。
三种思考的角度:
- 每个数(1)放左边(2)放右边(3)跳过,令 为左边的和, 为右边的和,那么要求的就是当 时, 的最大值。可以转换为选(1)就 + ,选(2)就 - ,否则跳过,然后得出一种用哈希表记录状态的DP算法: c++- int tallestBillboard(vector<int>& rods) { unordered_map<int, int> f; f[0] = 0; // 键值 (k, v) 表示 (总和,正数和) // 这里 总和 表示 正数和与负数和的差 for (int x: rods) { unordered_map<int, int> g(f); for (auto& t: g) { int s = t.first, a = t.second; f[s + x] = max(f[s + x], a + x); f[s - x] = max(f[s - x], a); } } return f[0];
- 从一般的DP角度考虑,可以想到定义 表示前 个数组成的高度差为 的时候的长度之和,下面是一个压缩成一维数组的版本,注意体会细节 c++- int tallestBillboard(vector<int>& rods) { int n = rods.size(); int sum = accumulate(rods.begin(), rods.end(), 0); vector<int> f(sum + 1); for (int i = 1; i <= n; i++) { auto g = f; for (int j = 0; j <= sum; j++) { // 钢筋高度差为 j 的时候 长度和至少为 j if (f[j] < j) continue; // 加到长的一侧 int k = j + rods[i - 1]; // 高者更高 g[k] = max(g[k], f[j] + rods[i - 1]); // 加到短的一侧 k = abs(j - rods[i - 1]); // 巧妙 g[k] = max(g[k], f[j] + rods[i - 1]); } f = g; } return f[0] / 2; }
- 复习下折半查找 c++- int tallestBillboard(vector<int>& rods) { int n = rods.size(); unordered_map<int, int> m1, m2; // (delta, score) delta 表示整数与负数的和 function<void(int, int, int, int, unordered_map<int, int>&)> dfs = [&](int l, int r, int sum, int s, unordered_map<int, int>& m) { if (l > r) { m[sum] = max(m[sum], s); return; } dfs(l + 1, r, sum, s, m); // 不选 dfs(l + 1, r, sum + rods[l], s + rods[l], m); // 放左边 dfs(l + 1, r, sum - rods[l], s, m); // 放右边 }; dfs(0, n / 2, 0, 0, m1); dfs(n / 2 + 1, n - 1, 0, 0, m2); int res = 0; for (auto& [sum, s]: m1) { if (m2.count(-sum)) res = max(res, s + m2[-sum]); } return res; }
