😊😊 😊😊
不求点赞,只求耐心看完,指出您的疑惑和写的不好的地方,谢谢您。本人会及时更正感谢。希望看完后能帮助您理解算法的本质
😊😊 😊😊
[蓝桥杯 2023 省 B] 冶炼金属
题目描述
小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V V V, V V V 是一个正整数,这意味着消耗 V V V 个普通金属 O 恰好可以冶炼出一个特殊金属 X,当普通金属 O 的数目不足 V V V 时,无法继续冶炼。
现在给出了 N N N 条冶炼记录,每条记录中包含两个整数 A A A 和 B B B,这表示本次投入了 A A A 个普通金属 O,最终冶炼出了 B B B 个特殊金属 X。每条记录都是独立的,这意味着上一次没消耗完的普通金属 O 不会累加到下一次的冶炼当中。
根据这 N N N 条冶炼记录,请你推测出转换率 V V V 的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况。
输入格式
第一行一个整数 N N N,表示冶炼记录的数目。
接下来输入 N N N 行,每行两个整数 A , B A,B A,B,含义如题目所述。
输出格式
输出两个整数,分别表示 V V V 可能的最小值和最大值,中间用空格分开。
样例 #1
样例输入 #1
3
75 3
53 2
59 2
样例输出 #1
20 25
提示
【样例说明】
当 V = 20 V=20 V=20 时,有: ⌊ 75 20 ⌋ = 3 , ⌊ 53 20 ⌋ = 2 , ⌊ 59 20 ⌋ = 2 \left\lfloor\frac{75}{20}\right\rfloor=3,\left\lfloor\frac{53}{20}\right\rfloor=2,\left\lfloor\frac{59}{20}\right\rfloor=2 ⌊2075⌋=3,⌊2053⌋=2,⌊2059⌋=2,可以看到符合所有冶炼记录。
当 V = 25 V=25 V=25 时,有: ⌊ 75 25 ⌋ = 3 , ⌊ 53 25 ⌋ = 2 , ⌊ 59 25 ⌋ = 2 \left\lfloor\frac{75}{25}\right\rfloor=3,\left\lfloor\frac{53}{25}\right\rfloor=2,\left\lfloor\frac{59}{25}\right\rfloor=2 ⌊2575⌋=3,⌊2553⌋=2,⌊2559⌋=2,可以看到符合所有冶炼记录。
且再也找不到比 20 20 20 更小或者比 25 25 25 更大的符合条件的 V V V 值了。
【评测用例规模与约定】
对于 30 % 30 \% 30% 的评测用例, 1 ≤ N ≤ 1 0 2 1 \leq N \leq 10^{2} 1≤N≤102。
对于 60 % 60 \% 60% 的评测用例, 1 ≤ N ≤ 1 0 3 1 \leq N \leq 10^{3} 1≤N≤103。
对于 100 % 100 \% 100% 的评测用例, 1 ≤ N ≤ 1 0 4 1 \leq N \leq 10^{4} 1≤N≤104, 1 ≤ B ≤ A ≤ 1 0 9 1 \leq B \leq A \leq 10^{9} 1≤B≤A≤109。
蓝桥杯 2023 省赛 B 组 C 题。
小白到进阶各种解法:
一、暴力枚举:😊
思路:
简化问题 :不要一上来就分析多条记录,而是分析一条记录,找出问题的本质;
分析题目性质:
1、A个O生成B个X,我们要求A个O恰好生成B个X的转化率,转化率分为最大转化个数 m a x V max_V maxV 和 最小转化个数 m i n V min_V minV;
m
i
n
V
min_V
minV:表示
A
个
O
A个O
A个O 恰好生成
B
个
X
B个X
B个X 的所需的最小消耗值;
m
a
x
V
max_V
maxV:表示
A
个
O
A个O
A个O 恰好生成
B
个
X
B个X
B个X 的所需的最大消耗值;
2、所求转化率必须是能够使 A个O恰好能生成B个X;不能多一个X也不能少一个X;也就是说:max_V 和 min_V都要能使得A个O恰好生成B个X。
3、
A
/
V
=
=
B
A/V == B
A/V==B 该公式随着
V
V
V 的增大而减少!可看作是单调递减的!
问题:
1、如何求:A个O恰好能生成B个X所需要的最大转化个数 max_V 和 最小转化个数 min_V 呢?
答: 可以直接循环暴力枚举V,从 V = 1 V=1 V=1 开始,但是有同学会问:那么循环到什么时候结束呢?首先 V V V 是将 A 个 O A个O A个O 转化为 B 个 X B个X B个X,假如 V > A V > A V>A,那还能转化吗?很明显是不能了,所以 V V V 的最大枚举到 A A A 即可!
循环体内部:判断 A / V = B A / V = B A/V=B 如果等于的话,则分别与 m a x V max_V maxV 和 m i n V min_V minV 取最值。若不等于的话,则枚举下一个 V V V ;当
加个优化:
i
f
(
A
/
V
<
B
)
if (A/V < B)
if(A/V<B) 则由于随着
V
V
V 的增大,
A
/
V
A/V
A/V的值单调递减,所以若往后继续枚举的话,
A
/
V
A/V
A/V 的值将永远不会再大于
B
B
B了! 而
V
V
V 从1开始枚举的时候,是最大的,
A
/
1
,
A
/
2
,
A
/
3
,
,
,
A/1,A/2,A/3,,,
A/1,A/2,A/3,,, 所以这时候可以直接
b
r
e
a
k
break
break!
时间复杂度:由于 需要循环枚举的是
V
V
V ,
V
V
V 的枚举范围取决于
A
A
A,而
A
A
A 的最大值是:
1
e
9
1e9
1e9,所以很容易超时,故该算法超时!
部分代码:针对一条记录而言:
int a, b;
cin >> a >> b;
int min_v = inf;
int max_v = 0;
for (int v=1; v <= a; v ++)
{
if (a/v == b) {
min_v = min(min_v, v);
max_v = max(max_v, v);
}
if (a/v < b) {
break;
}
}
针对多条记录:
而本题要求的是:多条记录,即你的两个
V
V
V 求出来要满足每条记录的
A
个
O
A个O
A个O 恰好生成
B
个
X
B个X
B个X 。
直接一图胜千言吧:批注:该图在下文二分里面也会用到:
假设当前是三条记录的话:那么我们要找的是一个公共的 V V V 值,能同时满足三条记录的最小转化值和最大转化值!!!!
而公共最小值的话:就是从最小值集合里面: [ m i n V 1 , m i n V 2 , m i n V 3 ] [min_V1,min_V2,min_V3] [minV1,minV2,minV3] 中挑选出最大的那个,对于 m a x V max_V maxV 也是同理!
这也是本题所求解的核心,就是叫你去求解这个!!!!!结论:最小值里选取最大值,最大值里面选取最小值!
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
const int inf = 0x3f3f3f3f;
int Min_V=0, Max_V=inf;
int main()
{
cin >> n;
while (n --)
{
int a, b;
cin >> a >> b;
int min_v = inf;
int max_v = 0;
for (int v=1; v <= a; v ++)
{
if (a/v == b) {
min_v = min(min_v, v);
max_v = max(max_v, v);
}
if (a/v < b) {
break;
}
}
//该条记录的最小转化值和最大转化值取完了以后再与公共的最大转化值和最小转化值进行取最值!
Min_V = max(Min_V, min_v);
Max_V = min(Max_V, max_v);
}
cout << Min_V << " " << Max_V << endl;
return 0;
}
二、二分优化:😊
思路:结论:最小值里选取最大值,最大值里面选取最小值!
首先思考上面的代码里面超时的代码部分是在哪里?显然外层循环的 n n n 组数据是改变不了的,而内存里面的 f o r for for 循环将会超时,由于查找的 V V V 的范围过大而产生超时,所以说我们应该优化的是这里!而内层 f o r for for 循环所做的事情是:一个一个 V V V 地去枚举,由于枚举次数过多而超时!所以我们可以试着去减少枚举的次数!
注意上面暴力枚举思路里面的一句话,是使用二分的关键!
m
i
n
V
min_V
minV:表示
A
个
O
A个O
A个O 恰好生成
B
个
X
B个X
B个X 的所需的最小消耗值;
m
a
x
V
max_V
maxV:表示
A
个
O
A个O
A个O 恰好生成
B
个
X
B个X
B个X 的所需的最大消耗值;
从这句话里面我们可以看出有 m i n V min_V minV 和 m a x V max_V maxV 之间必然也有一段 V V V 是能够使得 A 个 O A个O A个O 恰好能生成 B 个 X B个X B个X 的情况!
简化问题:依然是从一条记录开始分析。
这里再简化一下:因为我们是每消耗
V
V
V 个
O
O
O 转化成一个
X
X
X,
V
V
V 的个数越少,消耗越少,越省钱,所以先求:要用
A
个
O
A个O
A个O 恰好生产
B
个
X
B个X
B个X的最小消耗的
V
V
V 的个数是多少?
如下图所示:
绿色部分表示:
A
/
V
>
=
B
A/V >= B
A/V>=B 的,合法但是不完全精确!
红色部分表示:
A
/
V
<
B
A/V < B
A/V<B 的,表示完全不合法!
紫色部分表示:
A
/
V
=
=
B
A/V == B
A/V==B 的,是合法区域的值!
所以说最后要找的是:大绿色框框里面的部分:
开启上帝视角的话,找的应该是:
V
=
=
5
V==5
V==5 这个值!即绿色和紫色的交界处:
那么对于数字 5 的查找就很
e
a
s
y
easy
easy 了,直接将问题转化为找到第一个大于等于 5 的元素即可。
部分代码:即找到最小值!建议先不看这里
if (A/V <= B) R = mid;
else L = mid;
初始化:
L
=
0
,
R
=
A
+
1
,
m
i
d
=
L
+
R
>
>
1
;
L = 0, R = A+1,mid = L+R >> 1;
L=0,R=A+1,mid=L+R>>1;
如果
A
/
m
i
d
<
=
B
A/mid <= B
A/mid<=B 则说明此时的
V
V
V 过大了,则移动右指针
R
R
R 使得
V
V
V值的范围变下,则下次再取
V
V
V 值的时候也必然变小,因为:
L
+
R
>
>
1
=
=
m
i
d
=
=
V
L+R >> 1 ==mid ==V
L+R>>1==mid==V,
R
R
R变小则
V
V
V 变小!
L
L
L 变大,则
V
V
V也变大!
如果
A
/
m
i
d
>
B
A/mid > B
A/mid>B 则说明此时的
m
i
d
也就是
V
mid也就是V
mid也就是V,
V
V
V 偏小了但是合法,我们想看看当前这个
V
V
V 是否是恰好使得
A
/
m
i
d
恰好
=
B
A/mid 恰好= B
A/mid恰好=B 的最小的
V
V
V,因为当前的
V
V
V 我们并不确定它是否是 恰好等于
的关系,所以说,我们还需要往右试探比当前这个
V
V
V 更大的
V
V
V 来看是否合法,如果比当前的
V
V
V 更小,并且
A
/
V
>
=
B
A/V >= B
A/V>=B 则说明这个解更优!而如何枚举到比当前
V
V
V 更大的V呢?经过上段的分析可知:调整我们的
L
L
L 即可,使得它变大,所以此时应该执行的操作是:
L
=
m
i
d
L = mid
L=mid;
而接下来就是找 使得 A/mid == B 的最大V
了
建议先不看,自己推一遍,用:A=75 B=3 进行一遍模拟!
if (A/mid >= B) L = mid;
else R = mid; 这种情况也就是:A/V >= B 的情况!!!!
此种情况下,二分的边界值为最大的
V
V
V ,能够使得
A
个
O
A个O
A个O 恰好转化为
B
个
X
B个X
B个X 的最大转化值
V
=
=
7
V == 7
V==7!即找到下图的紫色框框里最靠右边的值!
…
对于这两段代码的选取:
找大于等于某个数的最左边的一个值:
关键是
i
f
(
<
=
)
if(<=)
if(<=) 里面的 <=
号!这样理解,如果你要找的是大于等于某个数最左边的值,那么就 <= 箭头朝左,加个等于号!
if (A/V <= B) R = mid;
else L = mid;
找大于等于某个数的最右边的一个值:
关键是
i
f
(
>
=
)
if(>=)
if(>=) 里面的 >=
号!这样理解,如果你要找的是大于等于某个数最右边的值,那么就 >= 箭头朝右,加个等于号!
if (A/mid >= B) L = mid;
else R = mid; 这种情况也就是:A/V >= B 的情况!!!!
如果是多条记录的话:
就是找最小值里面的最大值咯,比较每条记录的最小值,找最大的那个最小值
M
i
n
V
Min_V
MinV,而该值一定能够满足每条记录都恰好转化出
B
个
X
B个X
B个X。
就是找最大值里面的最小值咯,比较每条记录的最大值,找最小的那个最大值
M
a
x
V
Max_V
MaxV,而该值一定能够满足每条记录都恰好转化出
B
个
X
B个X
B个X。
一图胜千言!结论:最小值里选取最大值,最大值里面选取最小值!
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
int n; //n组数据!
int Min_v=0, Max_v=inf; //分别存储最小值集合里面最大的最小值,最大值集合里面的最小的最大值!
int main()
{
cin >> n;
while (n --)
{
int a, b;
cin >> a >> b;
//找能够使得 A个O 恰好转化为 B个X的最小转化值!
int l=0, r=a+1;
while (l + 1 != r)
{
int v = l + r >> 1;
if (a/v <= b) r = v; //找最小值的最大值,则是 <=,即找到合法区域里的左边界!
else l = v;
}
Min_v = max(Min_v, r);
//找能够使得 A个O 恰好转化为 B个X的最大转化值!
l=0, r=a+1;
while (l + 1 != r)
{
int v = l + r >> 1;
if (a/v >= b) l = v; //找最大值里的最小值,就是 >= 即找合法区域的右边界!
else r = v;
}
Max_v = min(Max_v, l);
}
cout << Min_v << " " << Max_v << endl;
return 0;
}
每个人都有一个觉醒期,但觉醒期的早晚决定个人的命运。
三、二元不等式😊
代码:
文章来源:https://www.toymoban.com/news/detail-432028.html
每个人都有一个觉醒期,但觉醒期的早晚决定个人的命运。
文章来源地址https://www.toymoban.com/news/detail-432028.html
到了这里,关于冶炼金属【暴力枚举 + 二分 + 二元不等式】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!