题解/算法 {3098. 求出所有子序列的能量和}

题解/算法 {3098. 求出所有子序列的能量和}

@LINK: https://leetcode.cn/problems/find-the-sum-of-subsequence-powers/;

虽然50很小 容易认为他是暴力, 但C(50, ?) 他是非常大的, 直接暴力肯定超时;

对A进行排序, 这样A的任意子序列[a1,a2,...,ak] 他的贡献 就等于min( a(i+1) - ai) 即相邻元素差;

看一个错误做法:
因此, 对于dfs( ind, cont, pre, mi), 表示 从[ind,...]开始枚举{选/不选}, 其前面[0...,ind-1]中 已经选择cont个数[...,pre](结尾是pre) 且其贡献为mi;

auto Get_DP = [&]( auto _dfs, int _ind, int _pre, int _cont, int _mi)->Mod_{
    if( _cont == K){
        return _mi;
    }
    if( _ind >= N){ return 0;}

    Mod_ RET = 0;
    RET += _dfs( _dfs, _ind+1, _pre, _cont, _mi); // 不选;
    RET += _dfs( _dfs, _ind+1, _ind, _cont+1, (_pre==-1?2e9: min(_mi, A[_ind]-A[_pre]));

    return RET;
};
return Get_DP( Get_DP, 0, -1, 0, 2e9);

这个代码会超时, 原因很简单, 你就是在枚举所有的C(N,K)而已;
于是接下来, 你想着 用记忆化来优化成DP, 然后发现 他的参数有4个, 需要50*50*50* 2e9的DP? 所以你会认为 记忆化DFS是不可行的; 其实这里你就错了, 因为最后那个2e9 你可以不用数组来存储 因为他是个离散值, 可以用map来存, 即使用map<int, Mod_> DP[51][51][51];来记录;
但即使这样, 还是超时了;
这是因为 你没有想清楚, 这个问题 他的DP定义 到底是什么, 而上来就写代码 把自己思路弄的很乱… 当你决定用DP(即记忆化DFS), 你要首先想好 你的DP定义|DP转移是什么, 确定之后 才能依据此来写DFS, 怎么能先写一个暴力DFS呢?

@DELI;

[0,...pre...,cur,...suf...], 对于一个状态[PRE, cur] 设其长度是Cont 贡献为Mi(Mi = [PRE,cur]里相邻元素的差的最小值), 此时 ...suf...有很多种选择方案 (比如其中选择了[s1,s2,s3] 那么就表示了一个[PRE, cur, s1,s2,s3]的方案), 对于后缀的所有选择方案 我们把他放到一个集合里(即一个DP状态), 用(cur, Cont, Mi)就表示 所有的以[PRE,cur]为前缀的所有方案 对答案的贡献; 以此作为DFS记忆化;
此时, 就不要再想你上面那个DFS了, 你可能认为 你这里的DP状态 不就是上面DFS里的(pre,cont,mi)吗? 把直接把之前DFS记忆化里面的ind给去掉不就行了(DFS函数参数里 依然有ind 只不过记忆化DP里没有他了, 即所有的DFS(?,pre,cont,mi) 都对应为DP[pre][cont][mi]);
. 这是错误的, 首先 这不符合记忆化DFS的要求(DFS函数参数必须与DP状态 严格对应, 你这里直接把ind去掉了); 其次 最最重要的是: 这两个 是完全不搭的, 之前的DFS 是枚举ind的{选/不选}而已, 可是 我们这里分析的DP 当你到达(cur,Cont,Mi)这个状态时, 哪有什么cur选/不选, 而是cur必须选 (这就是我们这里的DP就是这么定义的); 因此 要摈弃掉之前的DFS思路;

对于当前的(cur,Cont,Mi)(他就对应为DFS的参数), 我们要枚举 下一个元素nex, 即 将[PRE,cur] 变成[PRE,cur,nex], 因此正确代码是:

int sumOfPowers(vector<int>& A, int K) {
    int N = A.size();
    std::sort( A.begin(), A.end());
    using Mod_ = Modular<int,int(1e9)+7>;
    static unordered_map<int,Mod_> DP[51][51];
    FOR_( i, 0, 50){ FOR_( j, 0, 50){ DP[i][j].clear();}}
    auto Get_DP = [&]( auto _dfs, int _ind, int _cont, int _mi)->Mod_{
        if( DP[_ind][_cont].find( _mi) != DP[_ind][_cont].end()){
            return DP[_ind][_cont][_mi];
        }
        auto & curDP = DP[_ind][_cont][_mi];

        if( _cont == K){
            return curDP = _mi;
        }
        if( _ind >= N){ return curDP = 0;}

        Mod_ RET = 0;
        // 注意, `cont|mi`是已经包含了`ind`的! (否则你可能会认为 如何获取`pre` 不需要他的);
        FOR_( nex, _ind+1, N-1){
            RET += _dfs( _dfs, nex, _cont+1, std::min(_mi, A[nex]-A[_ind]));
        }

        return curDP = RET;
    };
    Mod_ ANS = 0;
    FOR_( i, 0, N-1){
        ANS += Get_DP( Get_DP, i, 1, 2e9);
    }
    return ANS;
}

递推DP

DFS就很复杂, 没有递推那么清楚;

int sumOfPowers(vector<int>& A, int K) {
    std::sort( A.begin(), A.end());
    int N = A.size();
    using Mod_ = ___Modulo_<int, int(1e9)+7>;
    std::unordered_map< int, Mod_> DP[ 51][ 51];
    {
        {
            DP[ 0][ 1][ int(2e9)] = 1;
        }
        FOR_( i, 1, N-1){
            auto & curDP = DP[i];
            curDP[ 1][ int(2e9)] += 1;
            FOR_( II, 0, i-1){
                FOR_( JJ, 1, K-1){
                    for( auto const& [delta, preDP] : DP[II][JJ]){
                        curDP[ JJ+1][ std::min(delta, A[i] - A[II])] += preDP;
                    }
                }
            }
        }
    }
    Mod_ ANS = 0;
    FOR_( I, 0, N-1){
        for( auto const& [k, v] : DP[I][ K]){
            ANS += ((Mod_)k * v);
        }
    }

    return (int)ANS;
}
  • 21
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值