题解/算法 {3117. 划分数组得到最小的值之和}

题解/算法 {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;
}
  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值