2020 年百度之星程序设计大赛 - 初赛三

2020 年百度之星·程序设计大赛 - 初赛三解题思路及代码(Discount、Game、Permutation)

1、Discount

 

Problem Description
学皇来到了一个餐馆吃饭。他觉得这家餐馆很好吃,于是就想办个会员。
一共有 nn 种会员充值卡套餐,假设学皇这餐饭的消费为 a 元,选择第 ii 种套餐,需要充值 b[i] * a 的钱,这次吃饭可以打 c[i]×10 折,由充值的钱支付(即这次吃饭只需要从充值金额中扣除 a×c[i] 元)。以后用剩余的充值的钱吃饭不再打折。
请问学皇应该选择哪个套餐(必须选择恰好一个套餐),使得优惠的比例最大?
优惠比例的定义是把充的钱用完以后,(本来应该付的钱 - 实际付的钱) / 本来应该付的钱。在这个题目里,实际付的钱就是这次充值的花费。

 

Input
第一行一个整数 test(1≤test≤100) 表示数据组数。
对于每组数据,第一行一个正整数 n(1 \leq n \leq 100)n(1≤n≤100) 表示套餐的数目。
接下来 nn 行,每行一个正整数 b[i] (1≤b[i]≤100) 和一个小数 c[i](0≤c[i]≤1,c[i] 最多包含两位小数)。
Output
对于每组数据,输出一个五位小数表示最大的优惠比例。如果小数点后超过五位,四舍五入到五位。

 

Sample Input
1
2
2 0.5
3 0.1
Sample Output
0.23077
样例解释
对于第一种套餐,优惠比例为 0.5a / (2a + 0.5a) = 0.2;
对于第二种套餐,优惠比例为 0.9a / (3a + 0.9a) = 9 / 39;

阅读题目,会感觉有点复杂,而且一些表达并不好理解,而这道题关键在于Sample Output,它给出了样例的解释说明,直观可以判断出题目最大优惠比例的计算方式,后续很容易解出题目。

Accept代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int test,i,j,n;
    float c[105],b[105];
    scanf("%d",&test);
    while(test--)
    {
        scanf("%d",&n);
        float r1=0.0,r=0.0;
        for(i=0;i<n;i++)
            scanf("%f%f",&b[i],&c[i]);
        for(i=0;i<n;i++)
        {
            if((1-c[i])/(b[i]+1-c[i])>r)//计算每种方案的优惠比例,取最大值
                r=(1-c[i])/(b[i]+1-c[i]);
        }
        printf("%.5f\n",r);    
    }
    return 0;
} 

2、Game

 

Problem Description
Alice 和 Bob 在玩游戏。
桌面上有两堆金币,少的那堆有 x 个金币,多的那堆有 2x 个金币。
假设金币可以被无限细分。Alice 和 Bob 事先都不知道 x 是几,但是他们都知道 x 是一个 (0,1] 之间均匀分布的随机实数。
Alice 会等概率的被分配到其中的一堆金币,Bob 会得到另一堆。xx 的值和两堆金币的分配是相互独立的。
拿到金币以后,Alice 会马上数清自己拿到多少金币。然后 Alice 可以选择是否和 Bob 那堆换。
给定 Alice 拿到的金币数目,请问 Alice 要不要交换,使得她期望能得到的金币数目更多?
如果交换期望得到的金币数目多于不交换期望得到的金币数目,输出交换,否则不交换。

 

Input
第一行一个正整数 test (1≤test≤200000) 表示数据组数。
接下来每行一个小数 p (0<p≤2),p 最多保留五位小数,表示 Alice 拿到的金币数目。
Output
对于每组数据,输出 Yes 表示需要交换,输出 No 表示不要交换。

 

Sample Input
1
1.00000
Sample Output
Yes

先看了题目,差点被误导为难题,其实看了程序太简单了,实际理解起来类似于博弈问题,当Alice在游戏中要获得更多期望金币,必须从概率方面进行预测。
简而言之,当p>1时,Alice一定就是得到最大那堆金币了,所以一定不交换,因为p=2*x,x 是一个 (0,1] 之间均匀分布的随机实数;反之,当p<=1,存在Bob可能获得最大金币,所以Alice选择交换可能获得最大金币。
虽然存在Alice拿到0.8,Bob拿到0.4或者1.6,但是还是交换为好,碰运气。

目前官方提示: 当 p > 1 时,alice 拿到的一定是大的那部分,所以一定不换; 否则,alice 交换以后有一半概率翻倍,一般概率减半,0.25+0.50.5=1.25,所以一定换。

Accept代码:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int test;
    float p;
    cin>>test;
    while(test--)
    {
        cin>>p;
        if(p>1.0)
            cout<<"No\n";
        else
            cout<<"Yes\n";
    }
    return 0;
}

3、Permutation

Problem Description
一开始有 n 个数,他们按 1…n 的顺序排列,要求交换最多 m 对数字(同一个数字可以参与多次交换),使得逆序对数目最大。
对于一个序列 A,如果存在正整数 i,j 使得 1≤i<j≤n 而且 A[i] > A[j]A[i]>A[j],则 <A[i],A[j]> 这个有序对称为 A 的一个逆序对。

Input
第一行一个正整数test (1≤test≤100000) 表示数据组数。
对于每组数据,一行两个整数 n,m (1≤n≤1000000,0≤m≤1000000) 表示数字个数和最多可以交换的数字对数。
Output
对于每组数据,一行一个整数表示答案。

Sample Input
6
1 1
2 0
2 1
3 1
4 1
4 2
Sample Output
0
0
1
3
5
6

看到题目,在纸上写几个简单样例,就很容易发现这是一个找数学规律的题目,可推导出3种情况,具体见代码理解不难。

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int test,n,m,i;
    scanf("%d",&test);
    while(test--)
    {
        scanf("%d%d",&n,&m);
        int nums=0;
        if(n==1||m==0)
            nums=0;
        else if(m>=n/2)//当交换次数>=数值向下取整的一半,退化为1+2+……+n-1之和
        {
//            for(i=1;i<=n-1;i++)
//                nums+=i;
                nums=n*(n-1)/2;//数学求和公式代替循环
        }
        else
        {
            int minum=n-2*m;//此题关键,推导一般的情况,得到的逆序对
//            for(i=minum;i<=n-1;i++)
//                nums+=i;
                nums=(minum+n-1)*(n-minum)/2;//数学求和公式代替循环
        }
        printf("%d\n",nums);
    }
    return 0;
}

目前官方提示:
当 m≥⌊n/2⌋ 时,一定能变成 n...1,这时逆序对数最大。否则我们依次交换 1 和 n,2 和 n-1,.....,一共交换 m 对。
但是一个问题暂未解决,测试完全正确,而出现超时问题,可以看到我用数学求和公式代替循环,考虑可以解决问题,但是提交居然错误,还未找出原因。希望有大佬可以评论区提出方法,或者之后我解决了会更新AC代码。

 


第三题更新啦,debug真实令人……,上面我的想法没错,可用数学公式求和来提高计算效率,提交为何错误呢?我找到了,尝试一个1000000的值,即n=1000000,m=500000可知,定义的变量越界了,所以需要都是长整型long long,小马虎让我不能AC。
所以上面代码稍微修改几个变量类型就OK。
Accept代码:

 

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
    ll test,n,m,i;
    scanf("%lld",&test);
    while(test--)
    {
        scanf("%lld%lld",&n,&m);
        ll nums=0;
        if(n==1||m==0)
            nums=0;
        else if(m>=n/2)//当交换次数>=数值向下取整的一半,退化为1+2+……+n-1之和
            nums=n*(n-1)/2;//数学求和公式代替循环,提高效率
        else
        {
            ll minum=n-2*m;//此题关键,推导一般情况公式,得到的逆序对
            nums=(minum+n-1)*(n-minum)/2;//数学求和公式代替循环,提高效率
        }
        printf("%lld\n",nums);
    }
    return 0;
}

 

其他方法大家可以参考我的CSDN文章评论区小伙伴提出来的哦。

 

 

 

我的博客园:https://www.cnblogs.com/chenzhenhong/p/13381296.html
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/107596506

 

————————————————
版权声明:本文为CSDN博主「Charzous」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Charzous/article/details/107596506

---------------------------------------------------------书上有路,学海无涯。 生活总是很忙碌,也许这才是生活真正的奥秘。--------------------------------------------------------- 作者:Charzueus 来源:博客园 本博文版权归作者所有! 禁止商业转载等用途或联系作者授权,非商业转载请注明出处! 版权声明:本文为博主原创文章,转载请附上原文出处链接和本声明。
posted @ 2020-07-26 18:13  Charzueus  阅读(686)  评论(0编辑  收藏  举报