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
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 (0Output
对于每组数据,输出 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
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≤iA[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
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
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;
}
文章评论区的小伙伴还有其他方法,大家可以参考哦。
我的博客园:https://www.cnblogs.com/chenzhenhong/p/13381296.html
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/107596506