• C++
  • B3959 [GESP202403 四级] 做题

  • @ 2025-6-4 19:59:11

B3959 [GESP202403 四级] 做题

题目描述

小杨同学为了提高自己的实力制定了做题计划,在第 kk 天时,他必须要完成 kk 道题,否则他就会偷懒。

小杨同学现在找到了一个题库,一共有 nn 套题单,每一套题单中有一定数量的题目。但是他十分挑剔,每套题单他只会使用一次,每一天也只能使用一套题单里的题目,之后那套题单就会弃之不用。对于每套题单,他不必完成题单内所有的题。

那么问题来了,小杨同学最多做题几天才偷懒呢?

输入格式

第一行,一个整数为 nn,表示有多少套题单。
第二行 nn 个整数 a1,a2,ana_1, a_2, \dots a_n,分别表示每套题单有多少道题。

输出格式

输出一行一个整数表示答案。

输入输出样例 #1

输入 #1

4
3 1 4 1

输出 #1

3

说明/提示

数据规模与约定

对全部的测试数据,保证 1n1061 \leq n \leq 10^61ai1091 \leq a_i \leq 10^9

2 条评论

  • @ 2025-6-4 20:03:50

    可以通过数学分析来消除内层 for 循环,核心思路是计算在当前题单数量 it->first 的限制下,最多能连续使用多少天,然后通过数学公式直接算出可增加的天数和消耗的题单数量。以下是优化后的代码及详细注释:

    #include<iostream>
    #include<map>
    using namespace std;
    
    map<int, int> myMap;
    
    int main() {
        int n;
        cin >> n;
        int a;
        for (int i = 1; i <= n; ++i) {
            cin >> a;
            myMap[a]++;  // 统计每个题量出现的次数,题量作为键,次数作为值
        }
    
        int k = 1;  // 当前要满足的天数,第k天需要做k道题
        int res = 0;  // 记录最终能坚持的天数
    
        // 遍历map,map会按键(题单数量)从小到大排序
        for (auto it = myMap.begin(); it != myMap.end(); ++it) {
            int current_a = it->first;  // 当前题单的题目数量
            int count = it->second;     // 该题单数量出现的次数
    
            // 如果当前题单数量小于当前需要满足的天数,无法用来做题,直接跳过
            if (current_a < k) {
                continue;
            }
    
            // 计算在当前题单数量的限制下,最多能连续填多少天
            // 理想情况下,从第k天开始,最多能连续用min(count, current_a - k + 1) 个题单来填
            // 因为每个题单只能用一天,且要满足第k天至少有k道题
            int max_use = min(count, current_a - k + 1);
            res += max_use;  // 增加能坚持的天数
            k += max_use;    // 更新到下一个需要满足的天数
        }
    
        cout << res << endl;
        return 0;
    }
    

    关键逻辑说明

    1. 条件判断
      if (current_a < k) ,若当前题单的题目数量比当前需要满足的天数还少,说明无法用它来完成第 k 天的做题任务,直接跳过该类题单。
    2. 计算可使用题单数量
      int max_use = min(count, current_a - k + 1);
      • count 是当前题单数量 current_a 对应的题单套数,代表有多少套这样的题单可用。
      • current_a - k + 1 是理论上最多能用几套当前题单。比如当前要从第 k 天开始填,题单有 current_a 道题,那么最多能覆盖到第 current_a 天(因为第 current_a 天需要做 current_a 道题,刚好匹配),所以能连续填的天数是 current_a - k + 1 天 ,对应要用这么多套题单。
      • 取两者较小值,是因为要么题单数量不够(count 小),要么题单能覆盖的天数有限(current_a - k + 1 小 ),取小的那个才能得到实际能使用的题单套数。
    3. 更新结果和天数
      • res += max_use; 把这一批题单能支撑的天数加到总天数里。
      • k += max_use; 完成 max_use 天后,下一个要满足的就是第 k + max_use 天了。

    这样通过数学推导,用简单的公式替换了内层循环,代码效率更高,尤其适合处理大规模数据(比如题目中 n10610^6 的情况 )。

    • @ 2025-6-4 19:59:55

      以下是添加详细注释后的代码:

      #include<iostream>
      #include<map>
      using namespace std;
      
      // 使用map统计每种题单数量的出现次数
      // key为题单题目数量,value为该数量出现的次数
      map<int, int> myMap;
      
      int main() {
          int n;
          cin >> n;  // 输入题单总数
          
          // 读取每个题单的题目数量并统计出现次数
          int a;
          for(int i = 1; i <= n; i++){
              cin >> a;
              myMap[a] += 1;  // 统计每种题单数量出现的次数
          }
          
          // 初始化变量:k表示当前天数,res表示最多能做题的天数
          int k = 1;
          int res = 0;
          
          // 遍历map,按题单数量从小到大处理
          for (auto it = myMap.begin(); it != myMap.end(); ++it) {
              // it->first 是题单的题目数量,it->second 是该数量出现的次数
              
              // 如果当前题单数量 >= 当前天数k,则可以使用该题单
              if(k <= it->first){
                  // 遍历该题单数量出现的次数
                  for(int i = 1; i <= it->second; i++){
                      // 检查当前题单是否能满足第k天的需求
                      if(k <= it->first){
                          res += 1;  // 可以满足,总天数+1
                      } else {
                          break;  // 无法满足,退出循环
                      }
                      k += 1;  // 处理下一天
                  }
              }
          }
          
          cout << res << endl;  // 输出最多能做题的天数
          return 0;
      }
      

      代码解释

      1. 数据结构选择

        • 使用 map<int, int> 统计每种题单数量的出现次数,自动按题单数量升序排列。
      2. 核心思路

        • 贪心策略:优先使用题目数量小的题单,以满足尽可能多的天数。
        • 天数递增:每天需要完成的题目数为 k,因此必须确保第 k 天使用的题单题目数 ≥ k
      3. 遍历逻辑

        • 从小到大遍历每种题单数量,对于每个题单数量:
          • 检查是否能满足当前天数 k 的需求。
          • 如果可以,使用该题单并将天数 k 递增。
          • 重复此过程直到无法满足或用完所有该数量的题单。
      4. 复杂度分析

        • 时间复杂度:O(n log n),主要来自 map 的插入和遍历。
        • 空间复杂度:O(n),最坏情况下每种题单数量都不同。

      示例说明

      对于输入:

      4
      3 1 4 1
      

      处理过程:

      1. 统计题单{1:2, 3:1, 4:1}
      2. 遍历题单
        • k=1:使用一个题单1,剩余 {1:1, 3:1, 4:1}res=1k=2
        • k=2:无法使用剩余的题单1,跳过
        • k=2:使用题单3,剩余 {1:1, 4:1}res=2k=3
        • k=3:使用题单4,剩余 {1:1}res=3k=4
        • 剩余题单1无法满足 k=4,结束。

      最终输出:3

      #include<iostream>
      #include<map>
      using namespace std;
      //bool tong[(int)1e9];//1byte  1int 4
      map<int, int> myMap;
      int main() {
      	int n;
      	cin >> n;
      	int a;
      	for(int i=1;i<=n;i++){
      		cin>>a;
      		myMap[a] += 1;
      	}
      	// 遍历 map
      	int k = 1;
      	int res = 0;
      	for (auto it = myMap.begin(); it != myMap.end(); ++it) {
      		//cout << it->first << " -> " << it->second << endl;
      		if(k <= it->first){
      			for(int i=1;i<=it->second;i++){
      				if(k <= it->first){
      					res += 1;
      					k += 1;
      				}else{
      					break;
      				}	
      			}
      		}
      	}
      	cout<<res<<endl;
      	return 0;
      }
      
      • 1