3 条题解

  • 0
    @ 2025-11-27 16:46:00
    #include <bits/stdc++.h>
    using namespace std;
    
    int main(){
        int n;
        cin >> n;
        
        // 修正1:开 n+1 的空间,适应 1-based 下标
        vector<long long> man(n + 1); 
        for(int i = 1; i <= n; i++){
            cin >> man[i];
        }
        
        int m, p1;
        long long s1, s2; // 修正3:涉及乘法计算的工兵数用 long long
        cin >> m >> p1 >> s1 >> s2;
        
        // 计算龙方初始气势
        long long qi1 = 0;
        for(int i = 1; i < m; i++){
            qi1 += man[i] * (m - i);
        }
        // 计算虎方初始气势
        long long qi2 = 0;
        for(int i = m + 1; i <= n; i++){
            qi2 += man[i] * (i - m);
        }
        
        // 神兵天降的气势
        long long qi3 = s1 * abs(p1 - m);
        if(p1 < m){
            qi1 += qi3;
        } else if(p1 > m){
            qi2 += qi3;
        }
        
        // 修正2:初始值不要超过 long long 范围
        long long diffmin = LLONG_MAX; // 或者9e18
        int ans = 1; // 初始化为 1 (题目要求编号最小)
        //遍历解空间(编号),找到让龙虎气势差距最小的位置
        for(int i = 1; i <= n; i++){
            //注意这里i>m时为负
            long long qi4 = s2 * (m - i); 
            // 这里的逻辑:
            // 原始差值 (龙-虎) + 变动值
            // 如果放龙边,qi4是正数,龙的优势增加
            // 如果放虎边,qi4是负数,相当于减去虎的增量,等同于虎的优势增加
            long long diffnew = abs(qi1 - qi2 + qi4);
            
            if(diffnew < diffmin){
                diffmin = diffnew;
                ans = i;
            }
        }
        cout << ans << endl;
        return 0;
    }
    
    • 0
      @ 2025-11-27 15:30:49

      你好!我是你的OI教练。

      这道题(NOIP 2018 普及组 T2 龙虎斗)是一道经典的模拟枚举题目。虽然它被归类为 T2,但核心逻辑其实不难,难点主要在于数据范围的坑细节处理

      下面我为你梳理一下解题思路:

      1. 最大的坑:数据类型(不开 long long 见祖宗)

      请务必先看一眼数据范围:

      • n105n \le 10^5(兵营数量)
      • ci,s1,s2109c_i, s_1, s_2 \le 10^9(工兵数量)

      计算一下气势的极限值: 气势 = 工兵数 ×\times 距离。 假设工兵有 10910^9,距离最远可以是 10510^5。 单格气势最大值 1014\approx 10^{14}。 而 int 的最大值约为 2×1092 \times 10^9结论int 存不下!如果不使用 long long,计算气势时会溢出变负数,导致全盘皆输。请确保所有涉及气势计算的变量都开 long long

      2. 第一步:计算初始气势(含天降神兵)

      在你的程序开始枚举 p2p_2 之前,你需要先算出当前的局势。

      1. 基础统计:遍历 1n1 \sim n 的兵营。
        • 如果在 mm 左边(i<mi < m),累加到“龙方气势”。公式:ci×(mi)c_i \times (m - i)
        • 如果在 mm 右边(i>mi > m),累加到“虎方气势”。公式:ci×(im)c_i \times (i - m)
      2. 处理天降神兵 (s1s_1)
        • 题目说 s1s_1 个兵降落在 p1p_1。这其实就相当于在枚举前,p1p_1 兵营的人数增加了。
        • 你可以在统计基础气势前直接修改数组 c[p_1] += s1
        • 或者算出基础气势后,再单独把 s1s_1 带来的气势加到对应的一方。

      3. 第二步:枚举策略(寻找最佳 p2p_2

      现在你手上有 s2s_2 个兵,你要选一个位置 p2p_2 放下去。 因为 nn 只有 10510^5,计算机每秒可以算 10810^8 次,所以我们完全可以暴力枚举每一个兵营!

      算法流程

      1. 记录一个变量 min_diff(最小差距),初始化为一个非常大的数(比如 9e18LLONG_MAX)。
      2. 记录一个变量 ans(最佳位置),初始化为 1。
      3. 循环 ii11nn(尝试把 s2s_2 放在第 ii 号兵营):
        • 假设放在 ii 号,计算新的龙方气势和虎方气势。
          • 其实不需要重新遍历全图计算,只需要在第一步算出的总气势基础上,加上 s2s_2 产生的气势即可。
        • 计算双方气势的差值绝对值:current_diff = abs(龙 - 虎)
        • 打擂台:如果 current_diff < min_diff
          • 更新 min_diff = current_diff
          • 更新 ans = i
      4. 循环结束后,输出 ans

      4. 细节提示:题目中的隐含条件

      题目要求:“如果存在多个编号同时满足最优,取最小的编号。”

      • 在写 if 判断时,只要写 if (current_diff < min_diff) 即可。
      • 不要写成 <=
      • 原因:因为你是从 11nn 循环的。
        • 假设在位置 2 算出的差距是 100。
        • 后来在位置 5 算出的差距也是 100。
        • 如果不更新(因为 100100 不小于 100100),ans 就会保留为 2。这正好符合“取最小编号”的要求。

      5. 代码逻辑框架

      #include <iostream>
      #include <cmath>
      using namespace std;
      
      int main() {
          // 1. 定义变量,注意 long long
          long long n, m, p1, s1, s2;
          long long c[100005];
          
          // 2. 输入数据
          
          // 3. 处理 p1 的天降神兵 (可以直接加到数组里,方便后面计算)
          // c[p1] += s1; 
          
          // 4. 计算当前的龙方总气势(dragon) 和 虎方总气势(tiger)
          // 遍历 1 到 n,利用 abs(i - m) * c[i] 累加
          
          // 5. 枚举 p2 (从 1 到 n)
          long long min_diff = 1e19; // 初始化无限大
          long long ans = 1;
          
          for (int i = 1; i <= n; i++) {
              long long current_dragon = dragon;
              long long current_tiger = tiger;
              
              // 加上 s2 带来的影响
              if (i < m) current_dragon += s2 * (m - i);
              else if (i > m) current_tiger += s2 * (i - m);
              
              // 计算差距
              long long diff = abs(current_dragon - current_tiger);
              
              // 更新答案
              if (diff < min_diff) {
                  min_diff = diff;
                  ans = i;
              }
          }
          
          // 6. 输出
          cout << ans << endl;
          return 0;
      }
      

      加油!只要开了 long long 并老老实实模拟,这道题就是送分题。


      这个版本有什么错误?

      #include <bits/stdc++.h>
      using namespace std;
      
      int main(){
          int n;
          cin>>n;
          // int pos[1e5+1];
          vector<long long> man(n);
          for(int i=1;i<=n;i++){
              cin>>man[i];
          }
          int m,p1,s1,s2;
          cin>>m>>p1>>s1>>s2;
          //计算龙方初始气势
          long long qi1=0;
          for(int i=1;i<m;i++){
              qi1+=man[i]*(m-i);
          }
          //计算虎方初始气势
          long long qi2=0;
          for(int i=m+1;i<=n;i++){
              qi2+=man[i]*(i-m);
          }
          //神兵天降的气势
          long long qi3=0;
          qi3=s1*abs(p1-m);
          if(p1<m){
              qi1+=qi3;
          }else if(p1>m){
              qi2+=qi3;
          }
          //遍历解空间(编号),找到让龙虎气势差距最小的位置
          long long diffmin=LLONG_MAX;
          int ans=0;
          for(int i=1;i<=n;i++){
              long long qi4=s2*(m-i);//注意这里i>m时为负
              long long diffnew=abs(qi1-qi2+qi4);
              if(diffnew<diffmin){
                  diffmin=diffnew;
                  ans=i;//因为从小到达遍历,所以第一个就是最小的
              }
          }
          cout<<ans<<endl;
      }
      

      你好!我是你的 OI 教练。

      你的代码思路非常清晰,用了数学方法(带符号的差值)来简化逻辑,这很棒!

      但是,这段代码有 3 个致命问题,其中一个会导致 RE(运行时错误/数组越界),另外两个会导致 WA(答案错误)

      我们一个个来修:

      1. 致命错误:Vector 下标越界 (导致 RE)

      vector<long long> man(n); // 这里只申请了 n 个空间,下标是 0 到 n-1
      for(int i=1;i<=n;i++){    // 你的循环是 1 到 n
          cin>>man[i];          // 当 i=n 时,访问了 man[n],越界!
      }
      

      原因vector<long long> man(n) 创建的动态数组,合法下标范围是 0n-1。 你后面所有的逻辑(包括输入和计算气势)都默认下标是从 1 开始的。当程序访问 man[n] 时,会发生非法内存访问。

      修正: 申请空间时多申请一个,或者直接把 NN 开大一点。

      vector<long long> man(n + 1); // 申请 n+1 个空间,这样下标 1 到 n 都是合法的
      

      2. 隐蔽错误:初始化值溢出 (导致 WA)

      long long diffmin = 1E20; // 错误!
      

      原因

      • long long 的最大值大约是 9×10189 \times 10^{18} (即 9e18)。
      • 102010^{20} (1e20) 已经大大超过了 long long 的范围。
      • 当你把 1e20 赋值给 long long 时,会发生溢出(变成一个奇怪的负数或者最大值截断),导致后面的 min 比较逻辑失效。

      修正: 使用 long long 范围内的极大值,或者 <climits> 里的 LLONG_MAX

      long long diffmin = 9e18; // 足够大了,且在范围内
      // 或者
      long long diffmin = LLONG_MAX; // 推荐写法
      

      3. 数据类型陷阱:输入变量类型 (导致 WA)

      int m, p1, s1, s2; // 这里定义了 int
      cin >> m >> p1 >> s1 >> s2;
      
      // 下面计算时:
      long long qi4 = s2 * (m - i); 
      

      原因

      • 题目中 s1,s2s_1, s_2 最大可达 10910^9。虽然它们本身能装进 int,但是涉及到计算时(比如 s1 * abs(p1-m)),结果极易超过 2×1092 \times 10^9
      • 虽然你在 qi4 左边写了 long long,但在 s2 * (m-i) 计算的那一瞬间,如果 s2s_2int,编译器可能会先用 int 算完(此时已经溢出变负数了),再转成 long long 赋值给 qi4

      修正: 把 s1, s2 直接定义为 long long

      int m, p1;
      long long s1, s2; // 必须是 long long
      

      你的“数学魔法”逻辑确认

      你用了一个很巧妙的写法:

      long long qi4 = s2 * (m - i);
      long long diffnew = abs(qi1 - qi2 + qi4);
      

      这个逻辑是正确的!

      • qi1 代表龙,qi2 代表虎。
      • qi1 - qi2 是它们原本的差距(带符号)。
      • i<mi < m(放在龙方),m-i 是正数,相当于给差距加上正值(龙变强)。
      • i>mi > m(放在虎方),m-i 是负数,相当于给差距加上负值(即减去,虎变强)。
      • 最后取 abs,正好就是新的气势差的绝对值。
      • 教练点评:这个写法很聪明,比写一堆 if-else 判断 ii 在左边还是右边要简洁得多。

      修改后的满分代码

      #include <bits/stdc++.h>
      using namespace std;
      
      int main(){
          int n;
          cin >> n;
          
          // 修正1:开 n+1 的空间,适应 1-based 下标
          vector<long long> man(n + 1); 
          for(int i = 1; i <= n; i++){
              cin >> man[i];
          }
          
          int m, p1;
          long long s1, s2; // 修正3:涉及乘法计算的工兵数用 long long
          cin >> m >> p1 >> s1 >> s2;
          
          // 计算龙方初始气势
          long long qi1 = 0;
          for(int i = 1; i < m; i++){
              qi1 += man[i] * (m - i);
          }
          // 计算虎方初始气势
          long long qi2 = 0;
          for(int i = m + 1; i <= n; i++){
              qi2 += man[i] * (i - m);
          }
          
          // 神兵天降的气势
          long long qi3 = s1 * abs(p1 - m);
          if(p1 < m){
              qi1 += qi3;
          } else if(p1 > m){
              qi2 += qi3;
          }
          
          // 修正2:初始值不要超过 long long 范围
          long long diffmin = 9e18; // 或者 LLONG_MAX
          int ans = 1; // 初始化为 1 (题目要求编号最小)
          
          for(int i = 1; i <= n; i++){
              // 你的数学逻辑很棒,保留!
              long long qi4 = s2 * (m - i); 
              // 这里的逻辑:
              // 原始差值 (龙-虎) + 变动值
              // 如果放龙边,qi4是正数,龙的优势增加
              // 如果放虎边,qi4是负数,相当于减去虎的增量,等同于虎的优势增加
              long long diffnew = abs(qi1 - qi2 + qi4);
              
              if(diffnew < diffmin){
                  diffmin = diffnew;
                  ans = i;
              }
          }
          cout << ans << endl;
          return 0;
      }
      

      改掉这三个小问题,这道题就 AC 了!

      • 0
        @ 2025-9-10 9:11:34

        C++ :

        #include<iostream>
        #include<cstdio>
        using namespace std;
        long long n,m,k,a[10000005];
        int main()
        {
        // 	freopen("fight.in","r",stdin);
        // 	freopen("fight.out","w",stdout);
            scanf("%lld",&n);
            for(int i=1;i<=n;++i)
                scanf("%lld",&a[i]);
            long long x,y;
            scanf("%lld%lld%lld%lld",&m,&x,&y,&k);
            if(m==1 || m==n)//因为都大于0,所以如果在1或n就可以直接输出m
            {
            	printf("%d",m);
            	fclose(stdin);
            	fclose(stdout);
            	return 0;
            }
            a[x]+=y;//把后来要加的加上
            long long l=0,r=0;
            for(int i=1;i<=n;++i)//算气势
            {
                if(i<m)l+=(long long)a[i]*(m-i);
                else r+=(long long)a[i]*(i-m);
            }
            if(l<r)//如果右边的气势比左边大,把“救兵”放左边
            {
                long long t=m-((r-l)/k);//这个程序的精华
                if((r-l)%k>k/2)t--;//精华#2,因为要算最近的,所以一波骚操作。还有考试的时候,就错这,t--写成t++了,对了就一等了.[哭][哭][哭]
                printf("%lld",max(t,(long long)1));//防止超界
            }
            else 
            if(l>r)//如果左边的气势比右边大,把“救兵”放右边
            {
                long long t=m+((l-r)/k);//精华
                if((l-r)%k>k/2)t++;//精华#2
                printf("%lld",min(t,(long long)n));//防止超界
            }
            else
            if(l==r)printf("%lld",m);//如有两边气势相同,就放在分界点
        // 	fclose(stdin);
        // 	fclose(stdout);
            return 0;
        }
        
        
        • 1

        信息

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