斜率dp回顾
# 斜率dp
对于类似的转移方程(为与有关的参数,为与有关的参数,为常数),可以通过单调性来优化
# 过程
对于,设,我们假设
假设,那么有
稍微化简一下,得
,假设(大于零则不等号不需改变)
则有
注意:若得到的不具有单调性,不可使用此方法!
令,则
显然,与斜率十分相似,该方法因此得名"斜率Dp"。
如图所示,假设可以转移自三个点,且。
考虑一下三种情况
对于情况1,根据可知点最优,同理,情况2时有或点最优,情况3时点最优。无论如何不可能为最优点,因而可以去除。
那么在继续引入任意个点后,有无可能使得为最优点呢?
如图所示,在引入新的点后,我们假设点为最优点,那么说明此时
又因为,所以,因此点优于点,与假设不符。
通过上述做法,我们能够得到一个下凸包(斜率单调递增,一般使用队列来维护)。对于给定的,只需要寻找线段,使得该线段为队列中第一段满足斜率的线段,一般可以使用二分法来解决。
特别的,对于,若单调递增,又因为维护的队列中斜率也是单调递增的,则可以通过把队头中的线段全部弹出,之后队头元素即为最优点。
总结一下,维护的凸包是上凸还是下凸,取决于不等号:
- 若是形如的式子,则维护下凸包
- 若是形如的式子,则维护上凸包
# 例题 Gym 102202E
首先,设长方形的面积为,宽为,高为,约定,所有长方形的面积之和为,所有长方形的宽之和为。
若选定两个长方形作为两侧的边界,设得到的容积为,则有
之所以是大于等于而非等于,是因为容器内可能存在比两侧长方形更高的长方形(即存在),但此类方案不可能为最优解,因而不影响最终结果。
得到上述面积公式后,可以把长方形按降序排列(升序也行,只不过公式稍微复杂一些)
按照斜率Dp的做法,可以得到如下式子
转移时,二分查找最优转移点即可。
AC代码如下:
#include <bits/stdc++.h>
#define ll long long
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;
using pll = pair<ll, ll>;
const int maxn = 3e5 + 1000;
ll n, sum_area = 0, sum_width = 0;
pll box[maxn];
int que[maxn], tail = 0;
ll bin_search(ll l, ll r, std::function<bool(ll)> check){
ll result = l, mid;
while(l <= r){
mid = (l + r) >> 1;
if(check(mid)){
result = mid;
r = mid - 1;
}else{
l = mid + 1;
}
}
return result;
}
inline ll area(int p){return box[p].first * box[p].second;}
ll get_ans(int i, int j){
if(box[i].first < box[j].first) swap(i, j);
return box[j].first * (sum_width - box[i].first - box[j].first) - sum_area + area(i) + area(j);
}
ll k;
bool check(ll mid){
return (box[que[mid]].first - box[que[mid + 1]].first) * k >= area(que[mid]) - area(que[mid + 1]);
}
void solve(){
cin >> n;
for(int i=1; i<=n; i++) {
cin >> box[i].first >> box[i].second;
if(box[i].first < box[i].second) swap(box[i].first, box[i].second);
sum_area += area(i);
sum_width += box[i].first;
}
sort(box + 1, box + 1 + n, [](pll a, pll b){
if(a.first != b.first) return a.first > b.first;
return a.second > b.second; // 第二关键字随意
});
ll ans = 0;
for(int i=1; i<=n; i++){
k = box[i].first; // 即W_k'
if(tail > 0){
ll pos = 0;
if(tail > 1){
pos = bin_search(0, tail - 2, check);
if(check(pos)) pos++;
}
ans = max(ans, get_ans(que[pos], i));
}
while(tail > 1 && (area(que[tail-1]) - area(i)) * (box[que[tail-2]].first - box[que[tail-1]].first)
<= (area(que[tail-2]) - area(que[tail-1])) * (box[que[tail-1]].first - box[i].first)) tail--;
que[tail++] = i;
}
cout << ans << '\n';
}
signed main(){
Android;
solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
上次更新: 2021/02/24, 03:37:30