「CodeNote」 HDU1171(01背包或母函数)

Posted by Dawn-K's Blog on June 26, 2020

HDU1171

题意

给n个物品, 然后后续n行, 每行两个数, v表示物品的价值, m表示物品的数量. 让你将物品分成尽可能接近的两堆. 多组输入, 当n<0时输入结束

1
2
3
0<n<=50
0<v<=50
0<m<=100

输出两个数A B, 中间以空格分开, 即分开的两堆的各自总价值, 要求A>=B

思路

思路有两个.

01背包, 这个不难想, 就是将物品根据数量继续拆分(就是将多组背包转化成01背包), 然后每个物品都设置其重量和价值相同, 然后设置一个大小为 sum/2 的背包, 进行01背包的计算. 最后 dp[sum/2] 就是所得两最接近的堆中的一堆, 答案就是 max(dp[sum/2],sum-dp[sum/2])

母函数, 这个是个母函数的模板题. 我们还是和01背包一样把相同的物品拆开, 然后对于每个物品, 都要么选, 要么不选. 也就是对于每个数, 都是 (1+x^v[i]) , 然后令母函数的最大项的次数是 sum/2 , 运算完成后, 从 sum/2 开始往0的方向找, 找到第一个系数不为0的项的次数, 就是所得两最接近的堆中的一堆, 和上个方法最后一步同样的处理可以得到答案.

另外, 对于母函数来说的话, 可以很方便的求出, 符合给定大小的背包的方法共有多少种.

代码

01背包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 01背包
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int v[5050];
ll dp[505000];
int main() {
    int n;
    while (cin >> n && n >= 0) {
        int pos = 0;
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            int tmp;
            int num;
            cin >> tmp >> num;
            for (int j = 0; j < num; ++j) {
                v[pos] = tmp;
                ++pos;
                sum += tmp;
            }
        }
        int V = sum / 2;
        for (int i = 0; i <= V; ++i) {
            dp[i] = 0;
        }
        for (int i = 0; i < pos; ++i) {
            for (int j = V; j >= v[i]; --j) {
                dp[j] = max(dp[j], dp[j - v[i]] + v[i]);
            }
        }
        dp[V] = max(dp[V], sum - dp[V]);
        cout << dp[V] << " " << sum - dp[V] << endl;
    }
    return 0;
}

母函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int v[5050], start[5050], numEnd[5050];
int main() {
    int n;
    while (cin >> n && n >= 0) {
        int pos = 0;
        int sum = 0;
        for (int i = 0; i < n; ++i) {
            int tmp;
            int num;
            cin >> tmp >> num;
            for (int j = 0; j < num; ++j) {
                start[pos] = 0;
                numEnd[pos] = 1;
                v[pos] = tmp;
                ++pos;
                sum += tmp;
            }
        }
        int V = sum / 2;
        
        // 最终要达到的数值,也就是次数的上限,p可以稍微大个1 2
        int P = V;
        // 实际上MAX = P+1即可
        int MAX = P + 1;
        // 项的个数
        int factorNum = pos;
        // 这两个数组其实开在这里不太好
        int result[MAX], tmpResult[MAX];
        // 虽然在这里一样,但是实际上可以把上面两个数组改成全局的,这里就可以起到优化效果了.
        int sz = min((P + 1) * sizeof(int), sizeof(tmpResult));
        memset(result, 0, sz);
        result[0] = 1;
        for (int i = 0; i < factorNum; ++i) {  // 逐个括号处理
            memset(tmpResult, 0, sz);
            // 遍历括号内部的序列
            for (int j = start[i]; j <= numEnd[i] && j * v[i] <= P; ++j) {
                // 遍历reslut数组,和已有的结果相乘,得到新的结果.
                for (int k = 0; k + j * v[i] <= P; ++k) {
                    tmpResult[k + v[i] * j] += result[k];
                }
            }
            memcpy(result, tmpResult, sz);
        }
        int check_num = V;
        while (check_num >= 0) {
            if (result[check_num]) {
                cout << max(sum - check_num, check_num) << " "
                     << min(sum - check_num, check_num) << endl;
                break;
            }
            --check_num;
        }
    }

    return 0;
}