01分数规划

文章详细介绍了实数二分算法模板及其两种常见应用,以及如何将其用于解决01分数规划问题,包括逃避考试场景中的最高总平均分计算和求解子串最大平均数的方法。通过实例和检查函数的实现,展示了如何通过二分法和动态规划求解此类优化问题。
摘要由CSDN通过智能技术生成

1 实数二分

实数二分模板有两种。

第一种:

double mid, l = L, r = R, eps = 1e-(k + 2);
while (r - l > eps) {
    mid = (l + r) / 2;
    if (/*condition*/) 
        l = mid;
    else 
        r = mid;
}

其中,k表示答案所要求的精度,即结果的精度精确到 1 0 − k 10^{-k} 10k

l = midr = mid以及条件视情况而定。所给的模板是寻找所有满足条件的实数中最大的(最靠右的),颠倒第5、7行改为寻找最小的(最靠左的)。

二分后r中的值是最后的答案。

第二种(较常用):

double mid, l = L, r = R;
for (int i = 1; i <= 100; i++) {
    mid = (l + r) / 2;
    if (/*condition*/)
        l = mid;
    else
        r = mid;
}

其中,二分过程一共进行了 100 100 100 次,即将区间中分出了 2 100 2^{100} 2100 个实数,因此精度相当高。若在二分过程中输出midlr的值可以发现, 100 100 100 次的二分完全绰绰有余。

它的具体使用方法与上一种相同,答案保存在r中。

2 01分数规划

2.1 思路

01分数规划: 给定若干个物体,其价值为 a i a_i ai,代价为 b i b_i bi,选出多个物体,使得能够最大化或最小化表达式
∑ a i w i ∑ b i w i ( w i ∈ { 0 , 1 } ) \frac{\sum a_iw_i}{\sum b_iw_i}(w_i\in\{0,1\}) biwiaiwi(wi{0,1})
其中, w i w_i wi (即 01 01 01)表示物体是否被选择。

解决01分数规划问题的经典思路:

double mid, l = L, r = R;
for (int i = 1; i <= 100; i++) {
    mid = (l + r) / 2;
    if (check(mid))
        l = mid;
    else
        r = mid;
}

利用模板寻找可能的解。

check()是一个判断mid是否满足题意的bool类型的函数,题目的核心就在于此。

利用题目所给关系式,推导出check()内的判断关系式或动态转移方程。

2.2 例题1:逃避考试

题目描述

一共 n n n 场考试,每场考试共有题目 b i b_i bi 道,可做对 a i a_i ai 道。所有考试的总平均分的计算方法为:
100 × ∑ i = 1 n a i ∑ i = 1 n b i 100\times \frac{\sum_{i=1}^n a_i}{\sum_{i=1}^n b_i} 100×i=1nbii=1nai
现可以逃避 k k k 场,求出可获得的最高总平均分。

题解

经典的分数规划题目。

最高总平均分,可知二分时应选择尽可能大的答案,所以在二分过程中,最终结果和 m i d mid mid 的关系始终保持:
∑ i = 1 k a i ∑ i = 1 k b i ≥ m i d ∑ i = 1 k a i ≥ m i d × ∑ i = 1 k b i ∑ i = 1 k a i − m i d × ∑ i = 1 k b i ≥ 0 ∑ i = 1 k ( a i − m i d × b i ) ≥ 0 \begin{aligned} \frac{\sum_{i=1}^k a_i}{\sum_{i=1}^k b_i} &\ge mid \\ \sum_{i=1}^k a_i &\ge mid \times \sum_{i=1}^k b_i\\ \sum_{i=1}^k a_i - mid \times \sum_{i=1}^k b_i &\ge 0 \\ \sum_{i=1}^k (a_i-mid \times b_i) &\ge 0 \end{aligned} i=1kbii=1kaii=1kaii=1kaimid×i=1kbii=1k(aimid×bi)midmid×i=1kbi00
所以,在check()中,可以用数组t[i]保存a[i] - mid * b[i]的值,进行排序,逃避最小前 k k kt[i],累计其余t[i]的值,若和大于等于 0 0 0,则解符合题意。

check()函数代码如下:

bool check(double mid) {
    for (int i = 1; i <= n; i++)
        t[i] = a[i] - mid * b[i];
    sort(t + 1, t + n + 1);
    double res = 0;
    for (int i = k + 1; i <= n; i++)
        res += t[i];
    return res >= 0;
}

2.3 例题2:平均数

题目描述

给一个长度为 n n n 的数列,找出该数列的一个子串,使得子串平均数最大化,并且子串长度 ≥ m \ge m m

题解

方法1:

推导关系式可得
∑ i = 1 k ( a i − m i d ) ≥ 0 ( k ≥ m ) \sum_{i=1}^k(a_i-mid)\ge 0(k\ge m) i=1k(aimid)0(km)
对数组t[]前缀和。对于每一个右端点 i ( m ≤ i ≤ n ) i(m\le i\le n) i(min),找到长度符合的左端点 j j j,使 ∑ p = 1 i − j + 1 t p \sum_{p=1}^{i-j+1}t_p p=1ij+1tp 尽可能的大.

如果存在 ∑ p = 1 i − j + 1 t p ≥ 0 \sum_{p=1}^{i-j+1}t_p\ge 0 p=1ij+1tp0,则解成立。

check()函数代码如下:

bool check1(double mid) {
    for (int i = 1; i <= n; i++) {
        t[i] = a[i] - mid;
        sum[i] = sum[i - 1] + t[i];
    }
    double minn = 2e9;
    for (int i = m; i <= n; i++) {
        minn = min(minn, sum[i - m]);
        if (sum[i] - minn >= 0)
            return true;
    }
    return false;
}

方法2:

相同地,求出 t t t 和前缀和 s u m sum sum

  • d p i dp_i dpi:以 i i i 为右端点的 t t t 子串最大和( m ≤ i ≤ n m\le i\le n min)。

  • d p m = s u m m dp_m=sum_m dpm=summ

  • d p m + 1 = m a x { d p m + t m + 1 , s u m m + 1 − s u m 1 } dp_{m+1}=max\{dp_m+t_{m+1},sum_{m+1}-sum_{1}\} dpm+1=max{dpm+tm+1,summ+1sum1}

  • d p m + 2 = m a x { d p m + 1 + t m + 2 , s u m m + 2 − s u m 2 } dp_{m+2}=max\{dp_{m+1}+t_{m+2},sum_{m+2}-sum_{2}\} dpm+2=max{dpm+1+tm+2,summ+2sum2}

. . . . . . ...... ......

  • d p i = m a x { d p i − 1 + t i , s u m i − s u m i − m } ( m ≤ i ≤ n ) dp_{i}=max\{dp_{i-1}+t_i,sum_{i}-sum_{i-m}\}(m\le i\le n) dpi=max{dpi1+ti,sumisumim}(min)

若存在 d p i ≥ n dp_i\ge n dpin,则解成立。

check()函数代码如下:

bool check(double mid) {
    for (int i = 1; i <= n; i++) {
        t[i] = a[i] - mid;
        sum[i] = sum[i - 1] + t[i];
    }
    dp[m] = sum[m];
    for (int i = m + 1; i <= n; i++) {
        dp[i] = max(dp[i - 1] + t[i], sum[i] - sum[i - m]);
        if (dp[i] >= 0)
            return true;
    }
    return false;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值