直播获奖

该博客介绍了如何使用C++解决[CSP-J2020]直播获奖问题。通过输入分数,利用排序和桶排序算法计算每个阶段的获奖分数线,并输出结果。优化思路包括替换sort函数以降低时间复杂度,采用桶排序方法来提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P7072 [CSP-J2020] 直播获奖 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

解题思路
1.输入分数,每输入一个分数,用sort函数排序
2.算出每次的获奖人数,向下取整
3.如果获奖人数为0,输出最大的分数
50分代码 
#include<bits/stdc++.h>
using namespace std;
int a[100005],b[100005];//创建数组 
bool cmp(int a,int b)
{
    return a>b;
}//对比函数 
int main()
{
    int n,w,t;
    cin>>n>>w;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sort(a+1,a+1+i,cmp);//sort排序 
        if(floor(i*w/100)==0)
        {
            

<think>嗯,用户问的是关于1946题“直播获奖”的C语言代码。首先,我需要确定这道题的具体要求。通常,这类题目可能涉及实时更新成绩并计算某个比例的名次分数线。比如,每次有新成绩进来,都要确定当前的前多少名中的最低分。 接下来,我得回忆一下NOI或者类似的竞赛题目中类似的题型。比如,假设题目是每次插入一个分数,然后立刻计算当前第k大的数,其中k可能是当前人数的某个比例,比如1%。这时候,如果直接每次排序的话,时间复杂度会很高,特别是当数据量大的时候,比如n到1e5的情况,这样的方法可能无法通过时间限制。 那用户可能需要的是一种更高效的数据结构来处理这个问题。常见的做法是使用堆,比如维护一个大小为当前人数的m(比如前w%的最小值,即第m大的数)的最小堆或者最大堆。例如,假设每次要找出当前第m大的数,其中m=max(1, floor(i*w%) ),i是当前人数。这时候,可以用一个大小为m的最大堆,堆顶就是第m大的元素。或者,使用两个堆,一个大顶堆保存前m个元素,小顶堆保存剩下的,这样堆顶就是所求的值。 但具体题目中的参数可能不同,比如w%可能是指当前人数的前w%向下取整,但至少1人。例如,当有i个人时,计算m = max(1, (int)(i * w / 100)),然后每次插入一个数,找出当前第m大的数。 这时候,如果使用快速选择算法,每次插入后找第m大的数,时间复杂度是O(n^2)在最坏情况下,可能无法通过。所以更高效的方法是维护一个堆结构,每次插入后调整堆的大小,保持堆的大小正好是m。这样每次插入的时间是O(log m),然后堆顶就是答案。 例如,当要维护前m大的元素,可以用一个最小堆。每当新元素进来,如果堆的大小小于m,或者新元素大于堆顶,就加入堆。如果堆的大小超过m,就弹出堆顶。这样,堆顶始终是前m大中的最小值,也就是第m大的数。这种情况下,每次插入的时间复杂度是O(log m),整体复杂度是O(n log m),对于n=1e5的情况应该是可以接受的。 那现在需要写这样的C语言代码。然而,C标准库中没有现成的堆结构,需要手动实现。所以代码的大致思路是:维护一个最小堆,堆的大小等于当前的m值。每次插入一个分数时,如果当前堆的大小小于m,或者分数大于等于堆顶,则将其插入堆中。插入后如果堆的大小超过m,则弹出堆顶。然后,堆顶元素就是当前的第m大的分数,也就是当次的结果。 但每次m的值是根据当前已插入的人数i来计算的,即m = max(1, i * w / 100)。例如,当i=1时,m=1,这时候堆顶就是第一个数。当i增加到10,w=10的话,m=1,所以每次都要维护一个大小为1的最小堆,堆顶是当前最大的元素,即每次输出最大的元素。这可能有问题,或者可能题目中的w是百分比,比如前w%中的最低分? 或者,题目中的要求可能是每次选取前⌊i×w%⌋名中的最低分。例如,当i=100,w=10时,前10名的最后一名就是第10大的分数。这时候,对于每个i,m=⌊i×w%⌋,如果这个值是0,则取1。 所以,正确的方法应该是维护一个大小为m的最小堆,其中m是当前i对应的值。每次i增加时,计算新的m,然后调整堆的大小。例如,当i从k变成k+1,m可能增加或者保持不变。这时候需要调整堆的大小,使得堆的大小等于新的m。这可能比较复杂,因为当m增加时,可能需要从剩下的元素中取出最大的加入到堆中。 或者,或许更高效的方式是维护两个堆:一个大顶堆保存前m大的元素,小顶堆保存剩下的元素。当m变化时,需要调整两个堆之间的元素。但这可能比较复杂,尤其是在动态变化m的情况下。 另一种思路是,不管m如何变化,维护一个有序数组,但这样插入的时间复杂度太高。 可能这道题的正确解法是使用计数排序,因为分数范围有限。例如,题目中分数最高是600,那么可以维护一个数组cnt[601],记录每个分数出现的次数。每次插入一个分数x,就cnt[x]++。然后当需要查询第m大的数时,从600倒序遍历,累加人数,直到总和≥m,此时的分数就是所求的结果。这种方法的时间复杂度是O(n * 600),对于n=1e5来说,总共有1e5*600=6e7次操作,可能刚好能通过时间限制。这在C语言中实现的话,速度足够。 例如,对于每次插入一个数,更新计数数组,然后计算当前i对应的m,然后从600倒序累加,找到第m大的数。这样的方法虽然每次查询的时间复杂度是O(600),但对于n=1e5来说,总时间是6e7次操作,这在竞赛中通常是可以接受的,因为一般时间限制是1秒,可以处理大约1e8的操作。 所以,这可能就是这道题的预期解法,特别是当分数范围较小的时候。因此,正确的C语言代码应该使用计数排序的方法。 现在我需要验证这个思路是否合理。例如,题目中的分数范围是0到600,那么使用计数数组是完全可行的。每次新分数进来,cnt[score]++。然后每次需要找第m大的数,就从600开始往下数,直到累加的人数达到或超过m。例如,当前i个人的m是max(1, floor(i * w / 100))。所以,当i=1时,m=1,累加cnt[600], cnt[599],...直到找到第一个存在的数,这里就是第一个分数。当i=10,w=10时,m=1,找最大的分数。当i=100,w=10时,m=10,找第10大的分数,也就是从高到低第10个分数。 所以,这样的方法可行。现在需要将这个思路转化为C代码。 代码的大致结构可能是: - 读取n和w。 - 初始化一个计数数组cnt,大小为605(假设分数范围是0~600)。 - 对于每个i从1到n: - 读取score,cnt[score]++. - 计算m = max(1, i * w / 100)。注意这里的除法是整数除法,向下取整。 - 然后从600开始,往下遍历,累加总人数sum,当sum >= m时,当前的分数就是答案,输出它。 这样,对于每个i,这一步的时间复杂度是O(600),对于n=1e5来说,总时间就是1e5 * 600=6e7次操作,这在C语言中应该是可以通过的。 需要注意的是,当i*w/100可能为0的时候,比如当i=1,w=50,这时候i*w/100=0.5,向下取整是0,这时候要取max(1,0)=1。所以,每次m的计算需要确保至少为1。 例如,在C代码中,m = (i * w) / 100; if (m ==0) m=1; 或者 m = max(1, (i *w)/100 ) 在C中,可以用条件表达式:m = i * w / 100; if (m <1) m=1; 或者更简洁地:m = (i * w +99)/100; 但这可能不正确,因为原题要求的是下取整后的结果乘以i的w%。例如,对于i=1,w=50,i*w=50,除以100是0.5,下取整是0,所以m=max(1,0)=1。而使用(i*w +99)/100的话,这会变成(50+99)/100=149/100=1,得到m=1,这正确。但是当i=2,w=50,则得到(100+99)/100=199/100=1,即1,而正确的m是2*50/100=1,所以此时是相同的。因此,或许可以直接用m = (i *w)/100;然后判断是否等于0,若等于0则设为1。 或者,正确的方式是: m = i * w / 100; if (m ==0) m=1; 或者: m = (i * w + 99) / 100; 这可能不正确,因为如果i*w刚好能被100整除,比如i=100,w=1,那100*1=100,除以100等于1,不需要加99。所以,正确的做法还是先计算m = (i *w)/100,然后如果m是0,则设为1。 例如: m = i * w / 100; if (m == 0) m = 1; 这样处理是正确的。 现在,将上述思路写成代码: 读取n和w,然后循环n次,每次读一个数score,更新cnt数组,然后计算m,再遍历cnt数组从600到0,累加sum,当sum >=m时,输出当前分数。 代码示例: #include <stdio.h> int cnt[601] = {0}; // 分数0~600 int main() { int n, w; scanf("%d%d", &n, &w); for (int i = 1; i <= n; i++) { int score; scanf("%d", &score); cnt[score]++; int m = i * w / 100; if (m == 0) m = 1; int sum = 0; for (int j = 600; j >=0; j--) { sum += cnt[j]; if (sum >= m) { printf("%d ", j); break; } } } return 0; } 这段代码是否正确? 测试一下样例。比如,假设输入n=3,w=60,每个分数是200, 300,400。那么: i=1时,m= (1*60)/100=0 → m=1。sum从600往下找,200的cnt是1,当j=400时cnt是0,300是0,200是1。sum=1 >=1,输出200?或者输入顺序是200,300,400的话: i=1时,score=200,cnt[200]++ → cnt[200]=1。m=1. 遍历j=600到0: j=600: cnt[600]是0 → sum=0.继续。 直到j=200时,sum +=1 → sum=1≥1,输出200。正确,因为此时第1名的分数线是200。 i=2时,score=300。cnt[300]++。i=2,m= (2*60)/100=1.2 → 1。sum需要找前1名。遍历到300的时候,sum是cnt[300]=1,所以输出300?是的,此时两个分数是200和300,前1名是300。所以输出正确。 i=3时,score=400。m=3*60/100=1.8→1。sum从400开始,cnt[400]=1,sum=1≥1 → 输出400。正确。 所以这段代码在样例中的表现是正确的。 再考虑另一个测试案例:n=10,w=10。每次i的m是1,1,1,1,1,1,1,1,1,1。所以每次都要输出当前最大的分数。例如,输入分数依次为1,2,3,…,10。那么每次的m是1。第i次的最大值就是i。所以输出应该是1,2,3,...,10。这段代码是否正确? 当i=1时,分数1,m=1。sum=1≥1 →输出1。正确。i=2,分数2,此时最大的数是2,输出2。是的。所以代码正确。 另一个测试案例:n=5,w=20。i=1→m=0.2→0→m=1。i=2→0.4→0→m=1。i=3→0.6→0→m=1. i=4→0.8→0→m=1. i=5→1→m=1. 所以每次都要输出最大值。例如分数依次为10,20,30,40,50。每次输出10,20,30,40,50。正确。 另一个情况,比如当多个相同分数时:比如输入三个分数都是500。当i=3,w=30→m=3*30/100=0.9→0→m=1. 需要找最大的三个500中的第1大,即500。代码是否正确?是的,sum从600到500,遇到cnt[500]是3,sum=3≥1→输出500。 综上,这段代码应该是正确的,并且时间复杂度在题目允许的范围内。</think>下面是对题目**1946. 直播获奖**的C语言代码实现及详细解析: ### 题目分析 题目要求根据实时插入的分数,动态计算当前获奖分数线。具体规则为:每当有新分数时,选取当前所有分数中排名前$w\%$(至少1人)的最低分数作为分数线。例如,当前有$i$人,则计算$m = \max(1, \lfloor i \times w\% \rfloor)$,并找出第$m$大的分数。 ### 解决思路 1. **计数排序法**:由于分数范围为$[0, 600]$,可以利用计数数组记录每个分数的出现次数。 2. **动态查询**:每次插入新分数后,从高分到低分累加人数,直到总和超过等于$m$,此时的分数即为答案。 ### 代码实现 ```c #include <stdio.h> int cnt[601] = {0}; // 分数范围0~600 int main() { int n, w; scanf("%d%d", &n, &w); for (int i = 1; i <= n; i++) { int score; scanf("%d", &score); cnt[score]++; // 更新计数数组 // 计算当前m值 int m = i * w / 100; if (m < 1) m = 1; // 保证至少1人 // 从高分向低分累加,寻找第m大的分数 int sum = 0; for (int j = 600; j >= 0; j--) { sum += cnt[j]; if (sum >= m) { printf("%d ", j); break; } } } return 0; } ``` ### 代码解析 1. **输入处理**:读取参赛人数$n$和获奖比例$w$。 2. **计数数组**:`cnt[601]`记录每个分数的出现次数。 3. **动态计算$m$**:根据当前人数$i$计算$m = \max(1, \lfloor i \times w\% \rfloor)$。 4. **查询分数线**:从最高分600开始向下遍历,累加人数直至总和≥$m$,输出当前分数。 ### 复杂度分析 - **时间复杂度**:每次插入和查询操作的时间为$O(600)$,总复杂度$O(n \times 600)$,可高效处理$n \leq 10^5$的数据规模。 - **空间复杂度**:固定为$O(601)$,与数据规模无关。 此方法通过利用分数范围有限的特性,避免了复杂数据结构,实现简洁高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值