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}
10−k。
l = mid
和r = 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 个实数,因此精度相当高。若在二分过程中输出mid
,l
和r
的值可以发现,
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\})
∑biwi∑aiwi(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=1nbi∑i=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=1kbi∑i=1kaii=1∑kaii=1∑kai−mid×i=1∑kbii=1∑k(ai−mid×bi)≥mid≥mid×i=1∑kbi≥0≥0
所以,在check()
中,可以用数组t[i]
保存a[i] - mid * b[i]
的值,进行排序,逃避最小前
k
k
k 个t[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=1∑k(ai−mid)≥0(k≥m)
对数组t[]
前缀和。对于每一个右端点
i
(
m
≤
i
≤
n
)
i(m\le i\le n)
i(m≤i≤n),找到长度符合的左端点
j
j
j,使
∑
p
=
1
i
−
j
+
1
t
p
\sum_{p=1}^{i-j+1}t_p
∑p=1i−j+1tp 尽可能的大.
如果存在 ∑ p = 1 i − j + 1 t p ≥ 0 \sum_{p=1}^{i-j+1}t_p\ge 0 ∑p=1i−j+1tp≥0,则解成立。
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 m≤i≤n)。
-
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+1−sum1}
-
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+2−sum2}
. . . . . . ...... ......
- 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{dpi−1+ti,sumi−sumi−m}(m≤i≤n)
若存在 d p i ≥ n dp_i\ge n dpi≥n,则解成立。
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;
}