题解/算法 {734. 能量石}
LINK: https://www.acwing.com/problem/content/description/736/
;
规定 最优解要选择全部的物品 (也就是, 对于价值为0的物品 我们也选上), 因此 答案为一个序列: T1, T2, ... Tn
;
暴力做法 即 n ! n! n! 枚举这个答案序列, 肯定可以找到答案 因为答案就是一个序列; (这个思路非常重要, 以下的所有优化 都基于此);
但要对他做优化, 如果用线性DP 他并不符合DAG的要求 (比如对于1,6
, 1可以在6的前面, 6也可以在1的前面);
-{
研究这个答案序列, 对于Ti, Tj (j=i+1)
, 令他们的S,E,L
分表示: 该物品的耗时, 该物品最初的价值, 每秒损失的价值;
令V(i)
表示 序列里第i
个物品的实际价值: 即max( 0, Ei - Time * Li)
其中Time表示前i-1
个物品的总耗时, 注意还要有个max(0)
操作 这非常重要;
pre (Ti, Tj) suf
, 交换他俩的次序 并不会影响pre,suf
对答案的贡献, 因此考虑贪心, 即排序;
令Time为pre
的总耗时;
你很容易认为: Ti, Tj
的实际价值为Vi + Vj = (Ei - Time * Li) + (Ej - (Time + Si) * Lj)
, 然后再得到Tj, Ti
的价值, 比较他俩的大小 (可以发现 虽然Time
是变量 但他会被消除掉, 结果之和S, L
有关 他俩都是确定值) 然后排序, 就得到了答案序列;
.
其实这是错误的… 因为, Vi, Vj
都还要进行一个max(0, ?)
的操作, 换句话说 你上面得到的Vi
是可能<0
的, 这是错误的;
那么对形如max( 0, (Ei - Time * Li)) + max( 0, (Ej - ...))
这个式子 确实又无法化简…
-}
也就是Vi + Vj = max( 0, Ri) + max( 0, Rj)
(用R 来表示 未进行max(0)
之前的真实值);
既然从上面已经知道, 如果Vi = Ri, Vj = Rj
的话, 他是可以排序的 那么我们就只考虑这种情况;
对于答案的T1, T2, ..., Tn
, 他一定是形如:>0, >0, ..., >0, 0, 0, ..., 0
即: 前面都是>0
, 后面都是0
(通过反证法可以证明);
假设T1, T2, ..., Tk
都是>0
的, 这些物品 他们都满足Vi = Ri
(即可以进行排序), 也就是 你对数组排序后 T1,T2,...,Tk
他们一定是排序后数组的一个子序列;
此时, 要看懂这个逻辑关键非常重要;
也就是, 对于T1...Tk
里的任意两个元素Ta, Tb
他俩是可以进行排序的 (因为他们都不会进行max(0)
操作), 由此推出 T1...Tk
在 排序后数组中 为一个子序列;
但是, 反之不然, 即对于排序后数组中的任意两个Ai, Aj
他俩在T1, T..., Tn
答案序列里的次序 你是不知道的 (因为假如Ai
需要涉及到max(0)
操作, 那么你的排序是错误的);
令A为排序后的数组, 此时需要: 枚举A
的任意子序列, 则一定可以枚举到答案T1...Tk
, 但这种方法是2^n
(即便你用DFS剪枝, 还是会超时);
原本他是n!
(即不符合线性DP的要求), 而现在是2^n
求的是子序列 (他符合线性DP的要求), 因此 用(i,j)
: A[0…i]中 选择了若干个物品后, 时间为j
的 最大价值;
int DP[ 102][ 10004];
void __Solve(){
static int id = 0; ++ id;
int ANS;
int N;
using Item_ = tuple< int, int, int>;
vector< Item_> A;
cin>> N;
A.resize( N);
for( auto & i : A) cin>> get<0>(i)>> get<1>(i)>> get<2>(i);
sort( A.begin(), A.end(), []( const Item_ & _a, const Item_ & _b){
auto si = get<0>( _a), li = get<2>( _a);
auto sj = get<0>( _b), lj = get<2>( _b);
return - si * lj > - sj * li;
});
// (i,j): A[0...i]中 选择了若干个物品后, 时间为`j`的 最大价值;
constexpr int Max_time_ = 1e4;
//> (0,?)
memset( DP[ 0], -1, sizeof( DP[ 0]));
DP[ 0][ 0] = 0;
DP[ 0][ get<0>( A[0])] = get<1>( A[0]);
//> (>0, ?)
for( int i = 1; i < N; ++i){
memcpy( DP[ i], DP[ i - 1], sizeof( DP[ i]));
auto s = get<0>( A[i]), e = get<1>( A[i]), l = get<2>( A[i]);
for( int j = 0; j + s <= Max_time_; ++j){
if( DP[ i - 1][ j] == -1) continue;
MAX_SELF_( DP[ i][ j + s], DP[ i - 1][ j] + max( 0, e - j * l));
}
}
ANS = *max_element( DP[ N - 1], DP[ N - 1] + Max_time_);
cout<< "Case #"<< id<< ": "<< ANS<< ED_;
}