Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

  • 动态规划

    • 动态dp学习笔记
    • 广义矩阵乘法学习笔记
    • 插头dp学习笔记
    • 斜率dp回顾
      • 过程
  • 暴力

  • 图论

  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

斜率dp回顾

# 斜率dp

对于类似dp[i]=max(dp[j]+Wi+Wj+Wi′Wj′+C)dp[i]=max(dp[j]+W_i+W_j+W_i'W_j'+C)dp[i]=max(dp[j]+Wi​+Wj​+Wi′​Wj′​+C)的转移方程(Wi,Wi′W_i,W_i'Wi​,Wi′​为与iii有关的参数,Wj,Wj′W_j,W_j'Wj​,Wj′​为与jjj有关的参数,CCC为常数),可以通过单调性来优化

# 过程

对于dp[k]dp[k]dp[k],设F(x)=dp[x]+Wk+Wx+Wk′Wx′+CF(x)=dp[x]+W_k+W_x+W_k'W_x'+CF(x)=dp[x]+Wk​+Wx​+Wk′​Wx′​+C,我们假设dp[k]=max(F(i),F(j)),(i<j<k)dp[k]=max(F(i),F(j)),(i<j<k)dp[k]=max(F(i),F(j)),(i<j<k)

假设F(i)<F(j)F(i)<F(j)F(i)<F(j),那么有dp[i]+Wi+Wk′Wi′<dp[j]+Wj+Wk′Wj′dp[i]+W_i+W_k'W_i'<dp[j]+W_j+W_k'W_j'dp[i]+Wi​+Wk′​Wi′​<dp[j]+Wj​+Wk′​Wj′​

稍微化简一下,得

(dp[j]+Wj)−(dp[i]+Wi)>Wk′(Wi′−Wj′)(dp[j]+W_j)-(dp[i]+W_i)>W_k'(W_i'-W_j')(dp[j]+Wj​)−(dp[i]+Wi​)>Wk′​(Wi′​−Wj′​),假设Wi′−Wj′<0W_i'-W_j'<0Wi′​−Wj′​<0(大于零则不等号不需改变)

则有

(dp[j]+Wj)−(dp[i]+Wi)Wi′−Wj′<Wk′\frac{(dp[j]+W_j)-(dp[i]+W_i)}{W_i'-W_j'}<W_k'Wi′​−Wj′​(dp[j]+Wj​)−(dp[i]+Wi​)​<Wk′​

注意:若得到的Wk′W_k'Wk′​不具有单调性,不可使用此方法!

令G(x)=dp[x]+Wx,H(x)=−WxG(x)=dp[x]+W_x,H(x)=-W_xG(x)=dp[x]+Wx​,H(x)=−Wx​,则

G(j)−G(i)H(j)−H(i)<Wk′\frac{G(j)-G(i)}{H(j)-H(i)}<W_k' H(j)−H(i)G(j)−G(i)​<Wk′​


显然,(1)(1)(1)与斜率十分相似,该方法因此得名"斜率Dp"。

如图所示,假设dp[k]dp[k]dp[k]可以转移自三个点A,B,CA,B,CA,B,C,且k2<k1k2<k1k2<k1。

考虑一下三种情况

  1. Wk′<k2<k1W_k'<k2<k1Wk′​<k2<k1
  2. k2<Wk′<k1k2<W_k'<k1k2<Wk′​<k1
  3. k2<k1<Wk′k2<k1<W_k'k2<k1<Wk′​

对于情况1,根据(1)(1)(1)可知AAA点最优,同理,情况2时有AAA或CCC点最优,情况3时CCC点最优。无论如何BBB不可能为最优点,因而可以去除。


那么在继续引入任意个点后,有无可能使得BBB为最优点呢?

如图所示,在引入新的点后,我们假设BBB点为最优点,那么说明此时WD′>k1W_D'>k1WD′​>k1

又因为k1>k2k1>k2k1>k2,所以WD′>k2W_D'>k2WD′​>k2,因此CCC点优于BBB点,与假设不符。


通过上述做法,我们能够得到一个下凸包(斜率单调递增,一般使用队列来维护)。对于给定的Wk′W_k'Wk′​,只需要寻找线段ABABAB,使得该线段为队列中第一段满足斜率sloopAB≥Wksloop_{AB}\ge W_ksloopAB​≥Wk​的线段,一般可以使用二分法来解决。

特别的,对于(1)(1)(1),若Wk′W_k'Wk′​单调递增,又因为维护的队列中斜率也是单调递增的,则可以通过把队头中sloop<Wk′sloop<W_k'sloop<Wk′​的线段全部弹出,之后队头元素即为最优点。


总结一下,维护的凸包是上凸还是下凸,取决于不等号:

  1. 若(1)(1)(1)是形如sloop<Wksloop<W_ksloop<Wk​的式子,则维护下凸包
  2. 若(1)(1)(1)是形如sloop>Wksloop>W_ksloop>Wk​的式子,则维护上凸包

# 例题 Gym 102202E

首先,设长方形iii的面积为AiA_iAi​,宽为WiW_iWi​,高为HiH_iHi​,约定Wi≥HiW_i\ge H_iWi​≥Hi​,所有长方形的面积之和为SA=∑AiS_A=\sum A_iSA​=∑Ai​,所有长方形的宽之和为SW=∑WiS_W=\sum W_iSW​=∑Wi​。

若选定两个长方形(i,j)(i≠j,Wi≥Wj)(i,j)(i\ne j,W_i\ge W_j)(i,j)(i=j,Wi​≥Wj​)作为两侧的边界,设得到的容积为VijV_{ij}Vij​,则有

Vij≥Wj∗(Sw−Wi−Wj)−(SA+Ai+Aj)V_{ij}\ge W_j*(S_w-W_i-W_j)-(S_A+A_i+A_j) Vij​≥Wj​∗(Sw​−Wi​−Wj​)−(SA​+Ai​+Aj​)

之所以是大于等于而非等于,是因为容器内可能存在比两侧长方形更高的长方形(即存在x∉{i,j},Hx>Wjx\not\in\{i,j\}, H_x>W_jx∈{i,j},Hx​>Wj​),但此类方案不可能为最优解,因而不影响最终结果。

得到上述面积公式后,可以把长方形按WiW_iWi​降序排列(升序也行,只不过公式稍微复杂一些)

按照斜率Dp的做法,可以得到如下式子

Ai−AjWi−Wj<Wk(i<j<k)若k从j转移优于从i转移\frac{A_i-A_j}{W_i-W_j}<W_k\ (i<j<k) \text{若k从j转移优于从i转移}Wi​−Wj​Ai​−Aj​​<Wk​(i<j<k)若k从j转移优于从i转移

转移时,二分查找最优转移点即可。


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
上次更新: 2021/02/24, 03:37:30
插头dp学习笔记
IDA*学习笔记

← 插头dp学习笔记 IDA*学习笔记→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式