You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

USACO青铜级别h-index算法问题:代码逻辑存疑、测试用例出错及时间复杂度超限求助

USACO h-index问题:代码错误排查与优化方案

问题回顾

你正在准备12月的USACO竞赛,练习cpid=1131的h-index问题。题目要求:Bessie有N篇论文,每篇引用量为ci,她可以撰写一篇综述最多引用L次(每篇论文最多被引用一次),求能达到的最大h-index(h是最大整数,满足至少h篇论文的引用量≥h)。

你的现有代码在某个测试用例(输入为1000 251,包含大量500、502的引用值)中输出500,但正确答案是501;同时处理N=1e5、L=5e4的大数据量时,代码因为时间复杂度太高而超时。下面我们来逐一分析问题并给出优化方案。

现有代码的核心问题

1. 逻辑漏洞:遗漏了关键的h候选值

你的代码只检查了以现有论文引用量为h的情况,完全忽略了可以通过消耗L次引用,将部分论文的引用量提升到比现有值大1的h场景。比如你提到的测试用例:

  • 假设有500篇引用量502的论文,500篇引用量500的论文,L=251。
  • 要达到h=501,只需要把1篇500的论文提升到501(消耗1次引用),这样就有501篇论文(500篇502+1篇501)满足≥501,这是可行的,但你的代码没有考虑这种情况。

2. 时间复杂度爆炸:频繁调用cite.count()

cite.count(x)是O(N)的操作,每次调用都会遍历整个数组。当你在循环中多次调用它时,总时间复杂度会飙升到O(N²),对于N=1e5的情况,必然会超时。另外,用集合去重的逻辑也没必要——因为数组已经排序,相同元素是连续的,直接跳过连续重复元素即可。

3. 特殊情况处理不完善

你最后针对n==1的判断过于局限,实际上即使n>1,也可能存在通过L将h提升到现有最大引用量+1的情况(当然前提是h≤n),而你的代码没有覆盖这类场景。

优化后的解决方案

核心思路:二分查找+前缀和+二分定位

因为h的取值范围是0到n(最多只有n篇论文,h不可能超过n),我们可以用二分查找快速定位最大的可行h值。配合排序后的数组和前缀和,能高效判断每个h是否可行:

  1. 先将引用数组升序排序,计算前缀和数组(快速计算区间和)。
  2. 二分遍历h的候选值,对于每个候选h:
    • 取数组中最后h篇论文(这是最大的h篇,最容易满足≥h的条件)。
    • 计算需要多少引用次数才能让这h篇论文都≥h:通过二分找到最后h篇中第一个≥h的位置,前面的论文需要提升到h,计算总缺口。
    • 如果总缺口≤L,说明h可行,尝试更大的h;否则尝试更小的h。

优化后的代码

import bisect

def main():
    import sys
    # 快速读取大量输入
    input_data = sys.stdin.read().split()
    ptr = 0
    n = int(input_data[ptr])
    ptr += 1
    l = int(input_data[ptr])
    ptr += 1
    cite = list(map(int, input_data[ptr:ptr+n]))
    cite.sort()
    
    # 计算前缀和数组,prefix[i]是前i个元素的和(prefix[0]=0)
    prefix = [0] * (n + 1)
    for i in range(n):
        prefix[i+1] = prefix[i] + cite[i]
    
    low = 0
    high = n
    max_h = 0
    while low <= high:
        mid = (low + high) // 2
        if mid == 0:
            # h=0一定可行,尝试更大的值
            max_h = mid
            low = mid + 1
            continue
        if mid > n:
            # 不可能有mid篇论文,缩小范围
            high = mid - 1
            continue
        
        # 取最后mid篇论文的起始索引
        start = n - mid
        # 在cite[start:]中找到第一个>=mid的位置
        pos = bisect.bisect_left(cite, mid, start, n)
        # 计算需要的引用次数:总需要的引用量 - 现有引用量
        required = mid * (pos - start) - (prefix[pos] - prefix[start])
        
        if required <= l and required >= 0:
            # 当前h可行,记录并尝试更大的h
            max_h = mid
            low = mid + 1
        else:
            # 当前h不可行,尝试更小的h
            high = mid - 1
    print(max_h)

if __name__ == "__main__":
    main()

代码说明

  • 输入优化:用sys.stdin.read()一次性读取所有输入,避免多次调用input()的开销,适合大数据量场景。
  • 排序与前缀和:排序后能利用二分快速定位,前缀和数组让区间和计算变为O(1)操作。
  • 二分查找:整体时间复杂度为O(N log N)(排序O(N log N),二分查找O(log N),每次判断用bisect是O(log N)),完全能处理N=1e5的情况。
  • 正确计算需求:通过bisect精准找到需要提升的论文数量,计算总缺口,确保逻辑正确,不会遗漏像h=501这样的候选值。

测试验证

针对你提到的测试用例,这个代码会正确计算出h=501:

  • 当mid=501时,start=1000-501=499,bisect找到cite[499:]中第一个≥501的位置是500(因为cite[499]=500,cite[500]=502)。
  • 需要的引用次数是501*(500-499) - (500) = 501-500=1,1≤251,因此h=501可行。

内容的提问来源于stack exchange,提问作者CoderTang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 13:22:41