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