HDU 6804 Contest of Rope Pulling:01背包+概率优化

HDU 6804 Contest of Rope Pulling:01背包+概率优化

题目

两个集合中各有 n , m n,m n,m个人,其中每个人有一个力量值 w i w_i wi和一个魅力值 v i v_i vi。从两个集合中各取出若干个人,满足力量值之和相等( ∑ i ∈ n w i = ∑ j ∈ m w j \sum_{i\in n} w_i=\sum_{j\in m}w_j inwi=jmwj),并且魅力值之和尽可能大( ∑ ( v i + v j ) \sum (v_i+v_j) (vi+vj)),输出最大的魅力值

数据范围

T T T组测试, 1 ≤ T ≤ 30 1\le T\le 30 1T30

每组测试中 1 ≤ n , m ≤ 1 0 3 ,   1 ≤ w i ≤ 1 0 3 ,   − 1 0 9 ≤ v i ≤ 1 0 9 1\le n,m\le 10^3,\ 1\le w_{i}\le 10^3,\ -10^9\le v_i\le 10^9 1n,m103, 1wi103, 109vi109

所有测试数据中, ( n + m ) ≤ 1 0 4 (n+m)\le 10^4 (n+m)104

题解

如果把其中一个集合中所有人的力量值取负数( − w i -w_i wi),该问题可以转化为:有 n + m n+m n+m个物品,每个物品用一对数 ( w i , v i ) ( − 1 0 3 ≤ w i ≤ 1 0 3 ,   − 1 0 9 ≤ v i ≤ 1 0 9 ) (w_i,v_i)(-10^3\le w_i\le 10^3,\ -10^9\le v_i\le 10^9) (wi,vi)(103wi103, 109vi109)描述。选择一个子集 S S S,满足 ∑ i ∈ S w i = 0 \sum_{i\in S}w_i=0 iSwi=0,同时最大化 ∑ i ∈ S v i \sum_{i\in S}v_i iSvi,是一个经典的01背包问题

d p [ i ] dp[i] dp[i]表示:取完前 x x x个物品后,当力量值为 i i i时的最大魅力值。可以通过将DP数组的中间值( d p [ m i d ] dp[mid] dp[mid])看作0点,最后的答案就是 d p [ m i d ] dp[mid] dp[mid]

虽然单个物品的 w i w_i wi最大只有 1 0 3 10^3 103,但在DP的过程中 ∣ ∑ w i ∣ |\sum w_i| wi可能达到 1 0 6 10^6 106,如果要把所有的可能取值都进行考虑,DP数组的大小会很大,复杂度是不允许的

为了减少 ∑ w i \sum w_i wi的范围,可以考虑以一种随机的方式安排物品的加入顺序,如此一来最优解在每一阶段对应的状态都在0附近振荡(考虑如果每次都是一正一负的取,就可以尽可能保证已考虑的 ∑ w i \sum w_i wi的值在0附近波动,从而减少DP数组的大小)。因此,只需要设一个足够大的上界,只考虑 ∑ w i \sum w_i wi的取值范围落在 [ − T , T ] [-T,T] [T,T]内,进行DP即可

数组随机化方法
#include <random>
mt19937 rnd(time(NULL));
shuffle(v.begin(), v.end(), rnd);

代码

#include <bits/stdc++.h>
#include <random>  // std::default_random_engine
using namespace std;
#define endl '\n'
typedef long long ll;
const int maxn = 1e6 + 10;
const ll INF = 1ll << 62;
mt19937 rnd(time(NULL));
struct node
{
    ll w, v;
};
int n, m;
vector<node> a;
ll dp[maxn];
int main()
{
    ios::sync_with_stdio(0), cin.tie(0);
    int T;
    cin >> T;
    while (T--) {
        a.clear();
        cin >> n >> m;
        for (int i = 1, w, v; i <= n + m; i++) {
            cin >> w >> v;
            if (i <= n)
                a.push_back({w, v});
            else
                a.push_back({-w, v});
        }
        shuffle(a.begin(), a.end(), rnd);  // 随机化数组
        fill_n(dp, maxn, -INF);  // 初始化DP
        ll ans = 0;
        int MID = 50000;
        dp[MID] = 0;  // 取中间点为0点
        for (int i = 0; i < n + m; i++) {
            if (a[i].w > 0) {  // w为正,从大到小更新01背包
                for (int j = 2 * MID; j >= a[i].w; j--) {
                    dp[j] = max(dp[j], dp[j - a[i].w] + a[i].v);
                }
            }
            else {  // w为负,同样是从“大”到“小”更新01背包,这里的大指的是离0点远
                for (int j = (a[i].w > 0 ? a[i].w : 0); j <= 2 * MID; j++) {
                    dp[j] = max(dp[j], dp[j - a[i].w] + a[i].v);
                }
            }
        }
        cout << dp[MID] << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值