题解/算法 {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]);
}