题解/算法 {3117. 划分数组得到最小的值之和}
@LINK: https://leetcode.cn/problems/minimum-sum-of-values-by-dividing-array/
;
容易想到一个DP做法: DP(i,j): 将[0...i]分成j个子数组 且每个子数组的&结果等于对应的T[]值, 记录各个子数组末尾元素之和的最小值
;
转换为(i,j) -> (ii, ?)
, 因为ii = [i+1, i+2,...]
因此可以用一个全局变量来动态的记录A[i+1,...,ii]的&结果
;
可是 他的时间是N * N * M = 1e9
, 这种代码就不要侥幸心理尝试了… 费力不讨好, 肯定超时的, 别浪费时间精力了…
#劣质做法#: 通过拆比特位来预处理(看所有0
出现的位置), 然后获取A[l...r]的&结果
就可以枚举所有比特位 看如果有0
那这个比特位结果就是0
;
.
不要一遇到二进制操作, 就想着拆位… 因为ii
是[i+1, i+2, ...]
依次遍历的, 你就使用一个全局变量 每次and &= A[ii]
即可;
这个做法 就是超时的, 不要再尝试他了, 即使你把递推方式 改成DFS记忆化, 然后尝试去剪枝, 也是不行的… 因为你的算法核心思想 本身就是超时的…
超时是因为: DP转移时 ii
的状态太多了, 即[>i, ..., N-1]
, 假如 我们不枚举ii
而是规定ii只能== i+1
呢?
此时要修改DP定义:
DP(i,j,k): 将`A[0...i]` 分成j个子数组 `S0, S1, ..., S(j-1)` 且`S[<(j-1)]的&值 == T[对应下标], `S[j-1]的&值 一定>= T[j-1]`, 且最小的`S0,S1,...,S(j-1)`末尾元素之和;
DP转移: (i,j,k) -> (i+1,?,?) 枚举A[i+1]要么和A[i]一组, 要么A[i+1]自己另新开一组;
这个DP 倒也不难, 问题是 时间是N*M*1e5
, 也是超时的;
此时, 你需要知道关于二进制操作的一个性质, 才能想到优化的策略: @LINK: (https://editor.csdn.net/md/?articleId=126951968)-(@MARK_2)
;
即对于(i,j,k)
其实k
合法的状态 只有20
个, 即时间是i*j * 20
而不是i*j*1e5
;
一个做法是用哈希, 即先得到所有以A[i]
结尾的子数组的&结果
(你只要得到了A[i-1]
结尾的所有结果 就可以推出来A[i]
结尾的), 然后哈希成[0,1,2,...]
, 即还是(i,j,k)
只不过这里的k<=20
; 这是效率最高的做法;
但这种做法 稍微有点麻烦 因为k
是个哈希值; 最简单的做法是我们就记录(i,j,k)
(k = 1e5
), 但不再使用FOR循环遍历的方式, 而是用map< (i,j,k), int> DP
来直接存所有状态 (即k
虽然等于1e5
我们不会遍历他来找合法状态 而是直接记录所有合法状态); 这里(i,j,k)
对应了一个(1e4, 10, 1e5)
的变长K进制数;
.
这也是递推DP的一个算法, 即用map
存储所有合法状态 来进行递推;
int minimumValueSum(vector<int>& A, vector<int>& T) {
int N = A.size();
int M = T.size();
// DP(i,j,k): 将`A[0...i]` 分成j个子数组 `S0, S1, ..., S(j-1)` 且`S[<(j-1)]的&值 == T[对应下标], `S[j-1]的&值 一定>= T[j-1]`, 且最小的`S0,S1,...,S(j-1)`末尾元素之和;
Integer_::Radix<int,int64_t> Rad; Rad.Initialize( {N+5, M+5, 1000006});
unordered_map<int64_t, int> DP;
if( A[0] >= T[0]){ DP[ Rad.VectorToInteger( {0, 1, A[0]})] = A[0];}
FOR_( dpI, 1, N-1){
decltype(DP) newDP;
auto Update = [&]( int64_t _k, int _v){
if( newDP.find( _k) == newDP.end()){ newDP[_k] = _v;}
else{ newDP[_k] = min( newDP[_k], _v);}
};
for( auto & [key,val] : DP){
auto dpJJ = Rad.GetBit(key, 1);
auto dpKK = Rad.GetBit(key, 2);
//> dpI和`dpI-1`同一组;
if( (dpKK & A[dpI]) >= T[ dpJJ-1]){
Update( Rad.VectorToInteger( {dpI, dpJJ, dpKK&A[dpI]}), val - A[dpI-1] + A[dpI]);
}
//> dpI和`dpI-1`不同一组;
if( dpJJ < M && dpKK==T[dpJJ-1]){
Update( Rad.VectorToInteger( {dpI, dpJJ+1, A[dpI]}), val + A[dpI]);
}
}
DP = std::move( newDP);
}
int ANS = -1;
if( DP.find( Rad.VectorToInteger({N-1, M, T.back()})) != DP.end()){
ANS = DP[ Rad.VectorToInteger({N-1, M, T.back()})];
}
return ANS;
}