冶炼金属(第十四届蓝桥杯CB)


小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X

这个炉子有一个称作转换率的属性 VV是一个正整数,这意味着消耗 V  个普通金属 O  恰好可以冶炼出一个特殊金属 X ,当普通金属 O  的数目不足 V  时,无法继续冶炼。

现在给出了 N  条冶炼记录,每条记录中包含两个整数 A  和 B ,这表示本次投入了 A  个普通金属 O ,最终冶炼出了 B  个特殊金属 X 

每条记录都是独立的,这意味着上一次没消耗完的普通金属 O  不会累加到下一次的冶炼当中。

根据这 N  条冶炼记录,请你推测出转换率 V  的最小值和最大值分别可能是多少,题目保证评测数据不存在无解的情况

输入格式

第一行一个整数 N ,表示冶炼记录的数目。

接下来输入 N  行,每行两个整数 AB 含义如题目所述。

输出格式

输出两个整数,分别表示 V  可能的最小值和最大值,中间用空格分开。

数据范围

对于 30% 的评测用例,1N≤10^2
对于 60% 的评测用例,1N ≤10^3
对于 100% 的评测用例,1N ≤10^41BA ≤10^9

输入样例:

3

75 3

53 2

59 2

输出样例:

20 25

样例解释

当 V =20 时,有:⌊75/20⌋=3⌊53/20⌋=2⌊59/20⌋=2,可以看到符合所有冶炼记录。

当 V=25 时,有:⌊75/25⌋=3⌊53/25⌋=2⌊59/25⌋=2,可以看到符合所有冶炼记录。

且再也找不到比 20 更小或者比 25 更大的符合条件的 V 值了。

数学方法:

解题代码:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;


int main()
{
     int n;
     cin>>n;
     int r=2e9,l=0;
    for (int i = 0; i < n; i ++ )
    {
        int a,b;
        cin>>a>>b;
        r=min(r,a/b),l=max(l,a/(b+1)+1);
    }
    cout<<l<<' '<<r<<endl;
    
    return 0;
}

解题思路:

我们已知转化率为V,则a个金属O可以冶炼成为b个金属X(这时候多余的废料肯定在0~V之间)。换句话来说,炼制b个金属X最少需要消耗b*v,那么就有b*Va

由于a个金属O只能冶炼成为b个金属X,冶炼不出来b+1个金属X,则a<(b+1)*V

那么我们整理一下就得到一个不等式,也就是

b*Va<(b+1)*V

整理一下我们最后得到的不等式为

a/(b+1) <V a/b

这里我们需要知道V的最小应该为(a/(b+1))+1,因为a/(b+1)<V,而不是a/(b+1)V。

我们这里用给出的案例用图解来解释一下

我们需要的是他们三个的交集,也就是[19.7,25],但是我们这里需要注意的是我们的矿石只能为整数,所以我们得需要向上取整,对于c++里面,我们只需要给左边的边界+1就行了,因为int会自动向下取整也就是[20,25]。也就是案例里面的答案

那么接下来该写代码,我们需要定义左右边界,也就是l,r。我们需要初始化l和r,为了保证我们的初始化有效,我们需要将l初始化成为无穷大,r初始化成为无穷小。然后我们需要将左边的边界不断向符合条件的左边界的最右边逼近,意思就是所有的左边界里面最大的,而右边界和左边界完全相反,是所有的右边界里面最小的。

这一步骤也就是代码段里面的

r=min(r,a/b),l=max(l,a/(b+1)+1);

等所有的循环完了之后再把l和r输出就行了


二分版本

代码如下

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;


int get(int a,int b)
{
    int l=1,r=1e9+10;
    while(l<r)
    {
        int mid=l+r>>1;
        if(a/mid>b) l=mid+1;
        else r=mid;
    }
    return r;
}
int main()
{
     int n;
     cin>>n;
     int r=2e9,l=0;
    for (int i = 0; i < n; i ++ )
    {
        int a,b;
        cin>>a>>b;
        r=min(r,get(a,b-1)-1);
        l=max(l,get(a,b));
    }
    cout<<l<<' '<<r<<endl;
    
    return 0;
}

解题思路:

本题和上面的数学的解法唯一的区别就是

数学解法这里是

  r=min(r,a/b),l=max(l,a/(b+1)+1);

而二分的是

r=min(r,get(a,b-1)-1);
l=max(l,get(a,b));

对于get(int a,int b)函数的整体的思路是先抄一下二分的模板,再分析一下当mid满足的时候,是l=mid+1还是r=mid。

我们这里进行分析

当a/mid>b时,说明当前的转化率mid符合条件,我们需要将左边界移动到mid+1(因为mid符合条件,所以我们需要将左边界移动到mid+1),最后我们返回的r就是需要答案。

最后写一下我在比赛时三分钟写的垃圾代码(TLE)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 1e4+10;
int a,b,r;
pair<int,int>p[N];
#define x first
#define y second
 int n;
int check(int r)
{
    for(;r>0;--r)
   for(int i=0;i<n;++i)
    if(p[i].x/r!=p[i].y)
    return r;
    return 0;
}

int main()
{
   
    cin>>n; 
    int r=1e9+10;
    for (int i = 0; i < n; i ++ )
    {
        cin>>p[i].x>>p[i].y;
        r=min(r,p[i].x/p[i].y);
    }
    int l=check(r)+1;
    cout<<l<<" "<<r<<endl;
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值