2 条题解

  • 0
    @ 2025-11-27 12:10:07
    #include <bits/stdc++.h>
    using namespace std;
    
    int main(){
        int n;
        cin >> n;
        int arr[105]; // 建议开稍微大一点的固定数组,不要用 int a[n]
        
        for(int i = 0; i < n; i++){
            cin >> arr[i]; // 修正1:用 i 做下标
        }
        
        int cnt = 0;
        // 第一层:枚举每一个目标数 arr[k]
        for(int k = 0; k < n; k++){ 
            bool found = false; // 标记:是否找到了组成 arr[k] 的两个数
            
            // 第二层:枚举第一个加数 arr[i]
            for(int i = 0; i < n; i++){
                // 第三层:枚举第二个加数 arr[j]
                // 修正问题:a<n 是可以的,只要 j 从 i+1 开始即可
                for(int j = i + 1; j < n; j++){
                    
                    // 修正3:比较的是数值 arr[...],而不是下标 k, i, j
                    if(arr[k] == arr[i] + arr[j]){
                        cnt++;
                        found = true; // 标记找到了
                        break; // 跳出最内层循环 (j循环)
                    }
                }
                if(found) break; // 修正4:如果在内层找到了,也要跳出中层循环 (i循环),去测试下一个目标数
            }
        }
        cout << cnt << endl;
        return 0;
    }
    
    • 0
      @ 2025-11-27 12:09:51

      P2141 这道题(NOIP 2014 普及组 T1)是一道非常经典的枚举(暴力)题目。虽然题目简单,但有一个非常容易踩的“坑”,导致很多同学拿不到满分。

      下面我给你梳理一下思路和注意事项:

      1. 题目核心意图

      我们要统计的是:集合中有多少个,可以由集合中另外两个不同的数相加得到。 关键点:求的是“有多少个这样的数”,而不是“有多少对加数”

      2. 思路一:三重循环(最直观,不容易错)

      因为数据范围很小,n100n \le 100,这意味着 O(n3)O(n^3) 的复杂度大约是 1003=1,000,000100^3 = 1,000,000 次运算,计算机完全可以在 1 秒内跑完。

      我们可以按照逻辑直接翻译:

      • 第一层循环:枚举每一个数,把它当作“被测验的目标数”(记为 target)。
      • 第二层循环:枚举数组中的一个加数(记为 a)。
      • 第三层循环:枚举数组中的另一个加数(记为 b)。
        • 判断条件:如果 a + b == target,且 abtarget 是三个不同位置的数(题目保证集合中数各不相同,所以只要下标不同即可)。
        • 计数并跳出:一旦找到了这样的一对 ab,说明这个 target 是符合条件的。计数器 +1,并且立刻 break 跳出内层循环(或者是跳出针对当前 target 的寻找)。
          • 为什么要跳出? 因为我们只关心 target 能不能被算出来,不关心它能被几种方式算出来。比如 5=1+45=1+4,也是 5=2+35=2+3,只要算出一次,5 这个数就算 1 个答案。

      3. 思路二:两重循环 + 标记数组(更快,更像高手的写法)

      如果你想优化一下,可以用空间换时间(利用桶/标记的思想):

      1. 先用一个布尔数组(或者 int 数组)exist,标记哪些数字在输入的集合中出现过。比如输入有 3,那么 exist[3] = 1
      2. 双重循环枚举所有可能的加数对 a[i]a[j]i<ji < j)。
      3. 计算和 sum = a[i] + a[j]
      4. 判断 sum 是否存在于原集合中(直接查 exist[sum] 是否为 true)。
      5. 关键去重
        • 如果 sum 存在,我们不能直接 ans++。因为可能有 1+4=51+4=52+3=52+3=5,这样 5 就会被算两次。
        • 你需要再开一个 counted 数组,记录这个数是否已经被统计过了。
        • 或者,如果你用思路一的方法,就不会有这个问题。

      4. 最大的坑(一定要看!)

      很多同学写成下面的逻辑,结果导致样例都过不去或者不得分:

      循环 i (第一个加数)
        循环 j (第二个加数)
          如果 (a[i] + a[j] 在数组里)
             计数器 ans++
      

      错误原因: 假如集合是 {1, 2, 3, 4, 5}

      • 1+4=51+4=5,你让 ans+1。
      • 2+3=52+3=5,你又让 ans+1。
      • 结果“5”这个数字被你贡献了 2 次答案。但题目问的是“有多少个数”,5 只能算 1 个。

      5. 总结建议

      对于 n100n \le 100 的数据,我推荐你使用思路一,逻辑最清晰:

      1. 读入数据。
      2. 外层循环枚举每一个数作为“目标”。
      3. 内层两重循环去尝试拼凑这个目标。
      4. 拼凑成功就 ans++break(处理下一个目标)。

      祝你 AC!快去试着写代码吧。

      但是这个target是两个另外两个元素之和是不确定的,应该怎么处理?

      你的困惑点非常棒,这正是这道题逻辑的核心所在!

      既然“不知道哪两个数相加等于哪个数”,我们就不能通过“算”直接得出答案,而是要通过**“试”(枚举)**来解决。

      计算机最擅长的就是把所有可能性都试一遍。我们可以采用**“固定嫌疑人,寻找证人”**的思路。

      核心思路:固定目标法(三重循环)

      既然我们不知道数组里哪个数是“和”,那我们就轮流假设每一个数都是那个“和”(Target),然后去数组里找有没有另外两个数加起来等于它。

      具体的步骤演示:

      假设输入数组是 a[] = {1, 2, 3, 4}

      第一轮:假设 a[0] (也就是 1) 是那个 Target

      • 我们需要在剩下的数里找:有没有两个数相加等于 1?
      • 我们遍历 1+2, 1+3, 2+3... 发现都没有。
      • 结论:1 不是我们要找的数。

      第二轮:假设 a[1] (也就是 2) 是那个 Target

      • 我们找有没有两个数相加等于 2?(注意题目说要是正整数集合)
      • 遍历发现没有。
      • 结论:2 也不符合。

      第三轮:假设 a[2] (也就是 3) 是那个 Target

      • 我们开始尝试找加数:
        • 1 + 2?等于 3!Bingo!
        • 既然找到了,说明 3 是符合条件的。
        • 计数器 +1
        • 重点:立刻停止针对 3 的查找(Break),转而去测试下一个数。如果不停止,后面可能还有别的组合也能算出 3,就会重复计数。

      第四轮:假设 a[3] (也就是 4) 是那个 Target

      • 1 + 2 = 3 (不等于4)
      • 1 + 3 = 4 (Bingo!)
      • 计数器 +1。Break。

      代码逻辑结构

      为了实现上面这个过程,我们需要三层循环(就像我之前提到的思路一):

      // 1. 外层循环:枚举每一个“嫌疑人” (Target)
      for (int k = 0; k < n; k++) {
          
          // 在这里加一个标记,防止同一个数被多次统计
          // 其实只要找到一次就可以直接 break 跳出内层循环
          
          // 2. 中层循环:枚举第一个加数
          for (int i = 0; i < n; i++) {
              
              // 3. 内层循环:枚举第二个加数
              for (int j = i + 1; j < n; j++) { // j从i+1开始,避免重复且避免自己加自己
                  
                  // 这里的 a[k] 就是我们要验证的 Target
                  // a[i] 和 a[j] 就是两个加数
                  
                  // 必须保证加数和结果不是同一个数(题目要求不同的数)
                  // 虽然 a[i]+a[j]==a[k] 且全是正整数时,k肯定不等于i或j,但为了逻辑严谨可以不管它,
                  // 只要关注下标 i, j, k 不重复即可,或者直接看数值。
                  
                  if (a[i] + a[j] == a[k]) {
                      ans++;    // 找到了!记一笔
                      goto next_target; // 找到了就不找了,立刻测试下一个嫌疑人 (用 break 加 标记 也可以)
                  }
              }
          }
          
          next_target:; // 标签位置
      }
      

      总结

      不要去想“谁和谁加等于谁”,而是要**“拿出一个数,问问剩下的数能不能拼出它”**。

      • Target 不确定? \rightarrow 用外层循环 k 把它变得确定。
      • 加数不确定? \rightarrow 用内层循环 ij 把所有组合都试一遍。

      试着把这个逻辑写成代码吧!记得处理好**“找到一个组合就停止,不要重复统计同一个 Target”**这个细节。

      这么写对吗?

      #include <bits/stdc++.h>
      using namespace std;
      
      int main(){
          int n;
          cin>>n;
          int a[n]={0};
          for(int i=0;i<n;i++){
              cin>>a[n];
          }
          int cnt=0;
          for(int t=0;t<n;t++){
              for(int a=0;a<n-1;a++){//可以写a<n吗?
                  for(int b=a+1;b<n;b++){
                      if(t==a+b){
                          cnt++;
                          break;//找到了就及时break避免多余检查
                      }
                  }
              }
          }
          cout<<cnt<<endl;
      }
      

      同学,这份代码有4个非常严重的逻辑错误,会导致无法运行或者答案错误。

      别急,我们一个个来修:

      1. 致命错误:输入数组下标越界

      cin >> a[n];
      

      错误原因

      • 数组下标是从 0n-1 的。a[n] 这个位置已经越界了(超出数组范围)。
      • 而且你在循环里一直往同一个位置 a[n] 读入数据,前面的数据全被覆盖了,数组里并没有存下所有的数。

      修改

      cin >> a[i]; // 用循环变量 i 作为下标
      

      2. 致命错误:变量名冲突(Shadowing)

      int a[n] = {0}; // 这里定义了数组叫 a
      // ...
      for(int a=0; a<n-1; a++){ // 这里定义了循环变量叫 a
      

      错误原因

      • 你在 for 循环里定义了一个整型变量 a,这会**屏蔽(遮挡)**掉外面的数组 a
      • 在这个循环内部,当你写 a 时,编译器认为它是那个循环变量(整数),而不是数组。这会导致你无法访问数组里的元素。

      修改: 请老老实实遵循 OI 界的传统,循环变量用 i, j, k

      • 外层 t (Target) 改为 k (习惯用法,或者保持 i 也行)。
      • 中层 a 改为 i
      • 内层 b 改为 j

      3. 逻辑错误:比较的是下标,不是数值

      if(t == a + b)
      

      错误原因

      • 这里 t, a, b 在你的代码里是下标(也就是第几个数)。
      • 题目要求的是**“集合中的数”**相加,而不是它们的“位置编号”相加。

      修改: 应该去数组里取值比较:

      if( arr[k] == arr[i] + arr[j] ) // 假设数组名改成了 arr,循环变量改成了 k, i, j
      

      4. 逻辑错误:break 跳出不完全(会导致重复计数)

      if(match){
          cnt++;
          break; // 只能跳出最内层的 b 循环
      }
      

      错误原因

      • break 只能跳出一层循环。
      • 假如目标数是 10,数组里有 2, 83, 7
        • 当找到 2+8=10 时,cnt 加 1,跳出 b 循环。
        • 但是!中间的 a 循环还在继续。它会继续走到 3,然后内层循环又找到 7
        • 3+7=10cnt 又加 1。
        • 结果:数字 10 被统计了两次,答案偏大。

      修改: 你需要一个机制来彻底停止对当前 Target 的检查。可以用一个 bool 标记。


      回答你的小问题

      //可以写a<n吗?

      回答可以。 虽然写 a < n 在逻辑上没错(因为内层 b 是从 a+1 开始的,如果 a 到了 n-1,内层循环根本不会执行),但通常为了严谨和微小的效率,写 a < n 也没问题,不会导致报错,仅仅是多一次无效判断。


      修改后的正确代码参考

      我把变量名改成了 arr 以免混淆,循环变量用了 i, j, k

      #include <bits/stdc++.h>
      using namespace std;
      
      int main(){
          int n;
          cin >> n;
          int arr[105]; // 建议开稍微大一点的固定数组,不要用 int a[n]
          
          for(int i = 0; i < n; i++){
              cin >> arr[i]; // 修正1:用 i 做下标
          }
          
          int cnt = 0;
          // 第一层:枚举每一个目标数 arr[k]
          for(int k = 0; k < n; k++){ 
              bool found = false; // 标记:是否找到了组成 arr[k] 的两个数
              
              // 第二层:枚举第一个加数 arr[i]
              for(int i = 0; i < n; i++){
                  // 第三层:枚举第二个加数 arr[j]
                  // 修正问题:a<n 是可以的,只要 j 从 i+1 开始即可
                  for(int j = i + 1; j < n; j++){
                      
                      // 修正3:比较的是数值 arr[...],而不是下标 k, i, j
                      if(arr[k] == arr[i] + arr[j]){
                          cnt++;
                          found = true; // 标记找到了
                          break; // 跳出最内层循环 (j循环)
                      }
                  }
                  if(found) break; // 修正4:如果在内层找到了,也要跳出中层循环 (i循环),去测试下一个目标数
              }
          }
          cout << cnt << endl;
          return 0;
      }
      
      • 1

      信息

      ID
      3584
      时间
      1000ms
      内存
      128MiB
      难度
      10
      标签
      递交数
      1
      已通过
      1
      上传者