典例营
TJOI2013 拯救小矮人:传送门 to luogu
对于所有跑出去了的人,让 ( a + b ) (a{+}b) (a+b) 小的先出去更优。 exchange argument \text{exchange argument} exchange argument 易证。
那么按照 ( a + b ) (a{+}b) (a+b) 排序。如果一个人能够出去,就出去;否则,考虑某个已经出去的人回到坑里,而当前人出去,让垫底的 ∑ a \sum a ∑a 最大。所以维护已经出去的人关于 a a a 的大根堆即可。
核心代码:这里 i d id id 就是按照 ( a + b ) (a{+}b) (a+b) 排序后的结果,而 p q pq pq 为 i n t \tt int int 类型的大根堆。
int ans = 0; long long bottom = 0;
for(int i=1; i<=n; ++i) bottom += a[i];
for(int i=1; i<=n; ++i){
if(bottom+b[id[i]] >= H){
++ ans, bottom -= a[id[i]];
pq.push(a[id[i]]);
}
else if(!pq.empty() && a[id[i]] < pq.top()){
bottom += pq.top(); pq.pop();
bottom -= a[id[i]]; pq.push(a[id[i]]);
}
}
知识桥
当 d p \tt dp dp 转移的条件是关于 d p \tt dp dp 值(或者只涉及参数)的简单大小比较,且值的转移是加法时, d p \tt dp dp 值可能会是个凸函数,此时可以用 闵可夫斯基和 维护。
就例题而言,设
f
(
i
,
j
)
f(i,j)
f(i,j) 为,已经考虑了前
i
i
i 个人,跑出去了
j
j
j 个人,跑出去的人的
∑
a
\sum a
∑a 最小是多少。有转移
f
(
i
+
1
,
j
)
=
min
{
f
(
i
,
j
)
,
f
(
i
,
j
−
1
)
+
a
i
+
1
}
(
s
u
m
a
−
f
(
i
,
j
−
1
)
+
b
i
+
1
⩾
H
)
f(i{+}1,j)=\min\{f(i,j),\;f(i,j{-}1){+}a_{i+1}\}\quad(sum_a-f(i,j{-}1)+b_{i+1}\geqslant H)
f(i+1,j)=min{f(i,j),f(i,j−1)+ai+1}(suma−f(i,j−1)+bi+1⩾H)
注意这个 d p \tt dp dp 式并不正确,因为它假定了跑出去的人的编号是从小到大的,因此需要 s o r t \tt sort sort 。但这个做法的好处是:马上我们就可以直接看出,依据什么进行排序,又需要用堆维护什么。也就是机械化的反悔贪心。
将状态视作 ( j , f ( i , j ) ) (j,f(i,j)) (j,f(i,j)) 这样一个点,那么转移的本质是 ( 0 , 0 ) , ( 1 , a i ) (0,0),(1,a_i) (0,0),(1,ai) 两个点构成的凸包,与 d p \tt dp dp 值的凸包作闵可夫斯基和。注:事实上我们并没有证明 d p \tt dp dp 值构成凸包,但我们可以坚信之。
转移的要求是 f ( i , j − 1 ) + a i + 1 ⩽ s u m a − H + a i + 1 + b i + 1 f(i,j{\rm-1})+a_{i+1}\leqslant sum_a-H+a_{i+1}+b_{i+1} f(i,j−1)+ai+1⩽suma−H+ai+1+bi+1,注意左侧必须是结果的式子,即直接更新 f ( i + 1 , j ) f(i{\rm+}1,j) f(i+1,j) 的值。然后我们要 让限制条件越来越宽松,故按照 ( a i + b i ) (a_i{+}b_i) (ai+bi) 从小到大排序。
找到使得 f ( i , j ) f(i,j) f(i,j) 有定义的最大的 j j j,记为 j 0 j_0 j0 。如果 f ( i , j 0 ) f(i,j_0) f(i,j0) 满足转移条件,那就是完整的闵可夫斯基和,直接将这个斜率 a i a_i ai 加入堆;否则考虑 f ( i , j 0 ) − f ( i , j 0 − 1 ) f(i,j_0)-f(i,j_0{-}1) f(i,j0)−f(i,j0−1) 与 a i a_i ai 的大小关系:
- 如果 a i a_i ai 不小于它,又因为这是凸包上最大的斜率,故 a i a_i ai 无法进行任何更新。
- 如果 a i a_i ai 比它小,那么 a i a_i ai 至少足以更新 f ( i , j 0 ) f(i,j_0) f(i,j0),所以去掉 f ( i , j 0 ) f(i,j_0) f(i,j0) 后进行闵可夫斯基和即可。——在代码实现上,体现为将优先队列的堆顶删除,将 a i a_i ai 插入。
为什么 a i a_i ai 足以更新 f ( i , j 0 ) f(i,j_0) f(i,j0) 呢?这就涉及到上面的排序了。限制条件在变宽松,所以 让 d p \tt dp dp 值变小的转移总是合法转移。
最后,它为什么是正确的。因为 f f f 值 随着转移进行而偏离目标,如 f f f 是 min \min min 时 j j j 越大 f f f 就越大。然后,两个连续被选的物品,显然前者拥有更差的限制是最好的(否则也可以调整为这种情况)。
训练营
JSOI2007 建筑抢修:传送门 to luogu
前 i i i 个建筑修好 j j j 个的最小时间花费 f ( i , j ) ← f ( i − 1 , j − 1 ) + T 1 ( i ) ⩽ T 2 ( i ) f(i,j)\leftarrow f(i{\rm-}1,j{\rm-}1)+T_1(i)\leqslant T_2(i) f(i,j)←f(i−1,j−1)+T1(i)⩽T2(i),按照 T 2 T_2 T2 排序后维护 T 1 T_1 T1 大根堆。
CF1251E2 Voting:传送门 to CF
稍微有所改变,因为转移条件跟人数挂钩,应该把人数设为 d p \tt dp dp 值。前 i i i 人都投了票,省下了 j j j 元,最小化倒戈的人数量。假设该人首个倒戈,有转移 f ( i , j ) ← f ( i − 1 , j − p i ) + 1 ⩽ n − m i f(i,j)\leftarrow f(i{\rm-}1,j{\rm-}p_i)+1\leqslant n-m_i f(i,j)←f(i−1,j−pi)+1⩽n−mi 。这里凸包的斜率成为了 1 p i 1\over p_i pi1,维护 p i p_i pi 的小根堆。
美术馆起火:在 d i d_i di 时刻前,分配连续的 b i b_i bi 时长以获得 v i v_i vi 价值,最大化之。
f ( i , j ) ← f ( i − 1 , j − v i ) + b i ⩽ d i f(i,j)\gets f(i{-}1,j{-}v_i)+b_i\leqslant d_i f(i,j)←f(i−1,j−vi)+bi⩽di,按照 d i d_i di 排序后维护 b i v i b_i\over v_i vibi 的堆。
补充信息
另一种反悔贪心,用堆维护,形如 ∑ f i ( x i ) \sum f_i(x_i) ∑fi(xi) 在 ∑ x i = k \sum x_i=k ∑xi=k 时的最值,事实上是归纳法容易证明的。即,改变的部分不能有可以划分成权值相等的子集。