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是否可行:
- 先将引用数组升序排序,计算前缀和数组(快速计算区间和)。
- 二分遍历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
相关产品推荐
相关产品推荐

