题解/算法 {3181. 执行操作可获得的最大总奖励 II}

题解/算法 {3181. 执行操作可获得的最大总奖励 II}

@LINK: https://leetcode.cn/problems/maximum-total-reward-using-operations-ii/;

将数组排序后, 问题转换为: 从中选择一个子序列s1, s2, s3, ... 满足Sum( si) < s[i+1];

首先思考一个问题, 答案有多大呢?
你可能认为 就是总和5e4 * 5e4, 这是错误的… 因为答案子序列... sn 因为Sum( s[<n]) < sn 因此 其实他最大 是2 * sn = 1e5;

定义DP: bool(i,j): 前i个元素里 是否存在和为j的子序列;

int maxTotalReward(vector<int>& A) {
    std::sort( A.begin(), A.end());
    int N = A.size();
    static bool DP[ int(1e5) + 10];
    std::memset( DP, 0, sizeof( DP[0]) * (A.back() * 2 + 10));

    DP[0] = 1;
    FOR_( I, 0, N-1){
        FOR_( J, 0, A[I]-1){
            DP[J + A[I]] |= DP[J];
        }
    }
    FORrev_( i, A.back()*2, 0){
        if( DP[i]){
            return i;
        }
    }
    return -1;
}

可是 他会超时的。。。 N * A[i] = 2.5 * 1e9

因为DP值记录的是bool, 我们考虑用bitset来优化 即把FOR(J)循环 优化为 bitset<1e5>的位运算操作;
每次用DP[0,1,2,3,...., A[I]-1] 去更新 DP[ A[i] + {0,1,2,3,...,A[I]-1}] 即进行二进制或|操作, 我们令preDP为 DP[0,1,2,...,A[I-1]-1]这些状态, 其他的比特位 即[>=A[I]]的 都为0, 然后执行DP |= (preDP << A[I])即可;

因此, 此时涉及到bitset的截取操作, 即给定一个bitset 我们要截取[l,...,r]这些比特位 其他的(即<l || >r的 都置为0;
这其实是个模板算法___Bitset_InterceptMiddle, 通过左右移动溢出 因为bitset会默认补0;

int maxTotalReward(vector<int>& A) {
    std::sort( A.begin(), A.end());
    int N = A.size();
    static bitset< int(1e5) + 10> DP, Temp;

    DP.reset();
    DP[0] = 1;
    FOR_( I, 0, N-1){
        Temp = DP;
        ___Bitset_InterceptMiddle( Temp, 0, A[I]-1);
        DP |= (Temp << A[I]);
    }
    FORrev_( i, A.back()*2, 0){
        if( DP[i]){
            return i;
        }
    }
    return -1;
}

其实 如果你不知道 怎么截取bitset 也还有其他补救措施 (而且上面代码 效率不是太好 因为频繁的Temp = DP), 我们可以利用单调性 因为数组是排序的 我们每次 都要维护 一个DP[0,1,2,..., A[I]-1]这个bitset, 而A[I]-1是单独递增的;
对于A[I], 令DPsmall为 DP[0,1,...,A[I]-1]的状态len = A[I] (表示DPsmall的有效长度 即[0...len-1]是有效的 其他都是0), 然后当我们更新到A[I+1]时 就从A[I]遍历到 (A[I+1]-1)DP值赋值到DPsmall里; 这个做法 效率更高 因为不会每次都Temp = DP;

DP[0] = 1;
DPsmall[0] = 1;  len = 1;
FOR_( I, 0, N-1){
    for( ; len < A[I]; ++len){
        DPsmall[ len] = DP[ len];
    }
    DP |= (DPsmall << A[I]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值