3 条题解

  • 0
    @ 2025-12-15 16:52:42

    这是一个功能完整的 C++ 数据生成器。它包含了题目要求的标准解法(用于生成 .out)和数据生成逻辑(用于生成 .in)。

    使用说明

    1. 将下方代码保存为 generator.cpp
    2. 编译并运行该程序。
    3. 程序会在当前目录下生成 1.in / 1.out10.in / 10.out 共 20 个文件。
    4. 数据覆盖了 N,QN, Q 的小范围测试、大范围测试、特殊性质(R=NR=N)、以及不同的数值分布情况(随机、全部相同、只有少数几个值等),符合 OJ 测试数据标准。

    Generator 代码

    /**
     * P10264 [GESP202403 八级] 接竹竿 - 数据生成器 (修正版)
     * 修复问题: 
     * 1. 将大数组移至全局,防止栈溢出导致的计算错误或 Crash。
     * 2. 确保 .in 文件完全写入并关闭后再读取。
     * 3. 包含标准题解逻辑用于生成 .out 文件。
     */
    
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <cstdio>
    #include <fstream>
    #include <random>
    #include <string>
    #include <cstring>
    #include <ctime>
    
    using namespace std;
    
    // ==========================================
    // Part 1: 标准题解逻辑 (全局变量版)
    // ==========================================
    
    const int MAXN = 15005;
    const int LOGN = 16; // 2^14 = 16384 > 15000
    const int INF = 1e9 + 7;
    
    // 全局数组,避免栈溢出
    int g_n, g_q;
    int g_a[MAXN];
    int g_nxt[MAXN];
    int g_f[MAXN][LOGN];
    int g_mx[MAXN][LOGN];
    
    void solve_one_case(istream& in, ostream& out) {
        if (!(in >> g_n)) return;
        
        // 注意:数据生成器保证 a 的下标从 1 开始
        for (int i = 1; i <= g_n; ++i) {
            in >> g_a[i];
        }
    
        // 1. 预处理 nxt 数组
        // pos[v] 记录数值 v 后面最近一次出现的位置
        // 值域 1-13,开 15 够用
        vector<int> pos(20, INF); 
        for (int i = g_n; i >= 1; --i) {
            g_nxt[i] = pos[g_a[i]];
            pos[g_a[i]] = i;
        }
    
        // 初始化倍增表的边界情况:N+1 代表序列结束
        // 这一步至关重要,防止上一组大数据残留影响
        for (int k = 0; k < LOGN; ++k) {
            g_f[g_n + 1][k] = g_n + 1;
            g_mx[g_n + 1][k] = INF; 
        }
    
        // 2. 构建倍增表 (ST表)
        for (int i = 1; i <= g_n; ++i) {
            if (g_nxt[i] != INF) {
                g_f[i][0] = g_nxt[i] + 1;
                g_mx[i][0] = g_nxt[i];
            } else {
                g_f[i][0] = g_n + 1;
                g_mx[i][0] = INF;
            }
        }
    
        for (int k = 1; k < LOGN; ++k) {
            for (int i = 1; i <= g_n; ++i) {
                int mid = g_f[i][k - 1];
                if (mid > g_n) {
                    g_f[i][k] = mid;
                    g_mx[i][k] = g_mx[i][k - 1]; // 保持 mid 的 INF 属性
                } else {
                    g_f[i][k] = g_f[mid][k - 1];
                    g_mx[i][k] = max(g_mx[i][k - 1], g_mx[mid][k - 1]);
                }
            }
        }
    
        // 3. 处理询问
        in >> g_q;
        while (g_q--) {
            int l, r;
            in >> l >> r;
            
            int cur = l;
            int ans = 0;
            
            while (cur <= r) {
                // 倍增跳跃
                for (int k = LOGN - 1; k >= 0; --k) {
                    if (g_mx[cur][k] <= r) {
                        cur = g_f[cur][k];
                    }
                }
                
                if (cur > r) break;
                
                // 无法跳跃,保留当前牌
                ans++;
                cur++;
            }
            out << ans << "\n";
        }
    }
    
    void run_solver(string in_file, string out_file) {
        ifstream fin(in_file);
        ofstream fout(out_file);
        
        int t;
        if (fin >> t) {
            while (t--) {
                solve_one_case(fin, fout);
            }
        }
        fin.close();
        fout.close();
    }
    
    // ==========================================
    // Part 2: 数据生成器逻辑
    // ==========================================
    
    mt19937 rng(std::random_device{}());
    
    int rand_int(int l, int r) {
        uniform_int_distribution<int> dist(l, r);
        return dist(rng);
    }
    
    void generate_data(int id) {
        string in_name = to_string(id) + ".in";
        string out_name = to_string(id) + ".out";
        
        // 1. 生成 .in 文件
        ofstream fout(in_name);
        
        int T = 5; 
        int N_min, N_max, Q_min, Q_max;
        bool subtask_2 = false; 
        int val_range = 13;     
        int pattern_type = 0;   // 0: random, 1: all same/seq, 2: 1,2,1,2..., 3: sparse values
    
        // 根据测试点 ID 配置参数
        if (id <= 2) {
            N_min = 10; N_max = 100;
            Q_min = 10; Q_max = 100;
        } else if (id == 3) {
            N_min = 50; N_max = 100;
            Q_min = 50; Q_max = 100;
            pattern_type = 1; 
        } else if (id <= 6) {
            T = 3; 
            N_min = 10000; N_max = 15000;
            Q_min = 10000; Q_max = 15000;
            subtask_2 = true;
            if (id == 6) pattern_type = 2; 
        } else if (id <= 8) {
            T = 2;
            N_min = 14000; N_max = 15000;
            Q_min = 14000; Q_max = 15000;
        } else {
            T = 2;
            N_min = 15000; N_max = 15000;
            Q_min = 15000; Q_max = 15000;
            if (id == 9) val_range = 3; 
            else val_range = 13;        
        }
    
        fout << T << "\n";
    
        for (int t = 0; t < T; ++t) {
            int n = rand_int(N_min, N_max);
            fout << n << "\n";
            
            // 生成序列 A
            vector<int> A(n);
            if (id == 3 && t == 0) { // 全相同
                int v = rand_int(1, 13);
                for(int &x : A) x = v;
            } else if (id == 3 && t == 1) { // 1..13 循环
                for(int i=0; i<n; ++i) A[i] = (i % 13) + 1;
            } else if (pattern_type == 2) { // 1, 2, 1, 2...
                for(int i=0; i<n; ++i) A[i] = (i % 2) + 1;
            } else {
                for(int i=0; i<n; ++i) A[i] = rand_int(1, val_range);
            }
    
            for (int i = 0; i < n; ++i) {
                fout << A[i] << (i == n - 1 ? "" : " ");
            }
            fout << "\n";
    
            // 生成询问
            int q = rand_int(Q_min, Q_max);
            fout << q << "\n";
            for (int i = 0; i < q; ++i) {
                int l, r;
                if (subtask_2) {
                    l = rand_int(1, n);
                    r = n;
                } else {
                    l = rand_int(1, n);
                    r = rand_int(l, n);
                }
                fout << l << " " << r << "\n";
            }
        }
        
        // 关键:必须先关闭输出流,确保内容写入磁盘
        fout.close(); 
        
        // 2. 读取 .in 文件并生成 .out 文件
        printf("Generating Case %d...", id);
        run_solver(in_name, out_name);
        printf(" Done.\n");
    }
    
    int main() {
        srand(time(0));
        // 生成 1 到 10 的测试点
        for (int i = 1; i <= 10; ++i) {
            generate_data(i);
        }
        return 0;
    }
    

    复杂度与设计思路说明

    1. 数据覆盖性

      • Subtask 1 (30分):测试点 1-3 生成了 N100N \le 100 的数据,覆盖了随机情况和特殊边界(全相同数组、循环数组)。
      • Subtask 2 (30分):测试点 4-6 生成了 R=NR=N 的情况,其中测试点 6 专门构造了 1 2 1 2 这种容易产生大量消除跳跃的序列。
      • Subtask 3 (40分):测试点 7-10 是完全随机的大数据。测试点 9 限制 Ai[1,3]A_i \in [1, 3],使得数值重复率极高,测试算法在密集匹配下的性能;测试点 10 则是标准的满值域 1.5×1041.5 \times 10^4 压力测试。
    2. 时间复杂度分析

      • 生成器部分:主要是随机数生成和文件 IO,复杂度为 O(N)O(N),非常快。
      • 求解器部分 (Solver):预处理 O(NlogN)O(N \log N),查询 O(Q×13×logN)O(Q \times 13 \times \log N)。对于 N,Q=1.5×104N, Q = 1.5 \times 10^4,总操作次数约为 $1.5 \times 10^4 \times 13 \times 14 \approx 2.7 \times 10^6$,远低于 1 秒的时间限制(通常 1 秒可处理 10810^8 级别)。因此生成 20 个文件只需几秒钟。
    3. 空间复杂度

      • 使用了 MAXN = 15005,二维数组 fmx 大小约为 15000×1515000 \times 15,内存占用极小(几 MB),远低于通常的 128MB 或 256MB 限制。
    4. C++ 版本

      • 代码使用了 <random> 库(C++11),符合 C++14 标准,兼容性好。
    • 0
      @ 2025-12-15 16:49:46

      这是一份符合 NOIP/CSP 要求的 C++14 标准题解代码。代码中包含了详细的思路注释和复杂度分析,方便教学和自学。

      核心思路回顾

      1. 贪心策略:对于当前卡牌 ii,如果它后面第一个相同的牌在位置 jj,且 jj 在查询区间 [L,R][L, R] 内,那么 (i,j)(i, j) 以及它们中间的所有牌一定会被消除。因为这类似一个栈结构,只有中间的消完了,ii 才能和 jj 碰面消除。如果 j>Rj > R 或者不存在,那么 ii 一定会留下来。
      2. 倍增优化 (ST表思想)
        • 预处理 nxt[i]:表示 ii 后面第一个同数值的位置。
        • 定义 f[i][k]:从 ii 开始,连续进行 2k2^k 次“消除跳跃”后到达的位置。
        • 定义 mx[i][k]:在上述 2k2^k 次跳跃中,所涉及到的最远的 nxt 坐标。这是为了判断在查询区间 [L,R][L, R] 内跳跃是否合法(即跳跃过程不能越过 RR)。
      3. 查询逻辑
        • 利用倍增数组,尽可能地向后跳(消除)。
        • 当无法再跳跃时(说明当前牌的匹配牌在 RR 之外),该牌保留(答案+1),然后强制向后移动一格,继续尝试消除。
        • 由于牌的点数 13\le 13,根据鸽巢原理,无法消除而被保留的情况最多发生 13 次,保证了极高的效率。

      标准代码 (C++14)

      /**
       * 题目: P10264 [GESP202403 八级] 接竹竿
       * 算法: 贪心 + 倍增 (Doubling) / ST表思想
       * 复杂度: 
       *   - 时间: 预处理 O(N log N),单次查询 O(C * log N),其中 C 为值域大小(13)。总时间 O(N log N + Q * C * log N)。
       *   - 空间: O(N log N) 用于存储倍增表。
       */
      
      #include <iostream>
      #include <vector>
      #include <algorithm>
      #include <cstdio>
      
      using namespace std;
      
      // 定义常量
      const int MAXN = 15005;  // N <= 1.5 * 10^4
      const int LOGN = 16;     // 2^14 = 16384 > 15000,取16够用
      const int INF = 1e9 + 7; // 无穷大,用于表示越界
      
      // 全局变量
      int n, q;
      int a[MAXN];
      int nxt[MAXN];         // nxt[i] 表示 a[i] 下一次出现的位置
      int f[MAXN][LOGN];     // f[i][k] 表示从 i 开始跳 2^k 步后的位置
      int mx[MAXN][LOGN];    // mx[i][k] 表示从 i 开始跳 2^k 步的过程中,最大的 nxt 值
      
      // 单组数据处理函数
      void solve() {
          cin >> n;
          for (int i = 1; i <= n; ++i) {
              cin >> a[i];
          }
      
          // 1. 预处理 nxt 数组
          // 使用 pos 数组记录数值 v 最近一次出现的下标
          // 从后往前扫描,这样 pos 记录的就是“后面第一个出现的位置”
          vector<int> pos(15, INF); 
          for (int i = n; i >= 1; --i) {
              nxt[i] = pos[a[i]]; // 当前值的下一个位置
              pos[a[i]] = i;      // 更新当前值的最新位置为 i
          }
      
          // 初始化倍增表的边界情况:N+1 代表序列结束
          for (int k = 0; k < LOGN; ++k) {
              f[n + 1][k] = n + 1;
              mx[n + 1][k] = INF; // 终点无法跳跃,设为无穷大
          }
      
          // 2. 构建倍增表 (ST表)
          // 初始化 k=0 的情况
          for (int i = 1; i <= n; ++i) {
              if (nxt[i] != INF) {
                  f[i][0] = nxt[i] + 1; // 跳跃一步:消除 [i, nxt[i]],到达 nxt[i] + 1
                  mx[i][0] = nxt[i];    // 这一步跳跃需要的右边界是 nxt[i]
              } else {
                  f[i][0] = n + 1;      // 无法消除,逻辑上指向终点(查询时会被 mx 限制住)
                  mx[i][0] = INF;       // 需要无穷大的右边界才能跳(即永远不可跳)
              }
          }
      
          // 递推计算 k > 0 的情况
          for (int k = 1; k < LOGN; ++k) {
              for (int i = 1; i <= n; ++i) {
                  int mid = f[i][k - 1]; // 跳 2^(k-1) 步到达的中转点
                  if (mid > n) {
                      // 如果前半段已经跳出去了
                      f[i][k] = mid;
                      mx[i][k] = mx[i][k - 1];
                  } else {
                      // 如果前半段还在范围内,继续跳后半段
                      f[i][k] = f[mid][k - 1];
                      // 路径上的最大 nxt 需求是两段需求的最大值
                      mx[i][k] = max(mx[i][k - 1], mx[mid][k - 1]);
                  }
              }
          }
      
          // 3. 处理询问
          cin >> q;
          while (q--) {
              int l, r;
              cin >> l >> r;
              
              int cur = l;
              int ans = 0;
              
              while (cur <= r) {
                  // 尝试使用倍增快速跳跃
                  // 只要跳跃路径上的最大 nxt 值 <= r,说明这些跳跃在当前区间 [l, r] 内是合法的
                  for (int k = LOGN - 1; k >= 0; --k) {
                      if (mx[cur][k] <= r) {
                          cur = f[cur][k];
                      }
                  }
                  
                  // 经过上面的循环,cur 已经到达了“能消除的最远位置”
                  if (cur > r) break; // 如果已经跳出区间,结束
                  
                  // 如果还没出区间,说明当前卡牌 a[cur] 在 [cur, r] 范围内无法消除
                  // 原因:它的 nxt[cur] > r 或者不存在
                  ans++;  // 答案+1(保留这张牌)
                  cur++;  // 强制向后移一位,继续处理
              }
              cout << ans << "\n";
          }
      }
      
      int main() {
          // 开启 IO 优化
          ios::sync_with_stdio(false);
          cin.tie(nullptr);
          
          int t;
          if (cin >> t) {
              while (t--) {
                  solve();
              }
          }
          return 0;
      }
      

      易错点与关键点注释

      1. 倍增的有效性判断 (mx 数组)

        • 普通的倍增(如 LCA)只需要看父节点是否存在。
        • 本题关键:跳跃是否合法取决于“跳跃跨度”是否被查询区间 [L,R][L, R] 包含。如果某次跳跃依赖的配对点位置 nxt 超过了 RR,这次跳跃就不能发生。
        • 所以维护 mx[i][k] 是必须的,它记录了 2k2^k 步内涉及到的最远的索引。
      2. 时间复杂度的保证

        • 代码中有双重循环:外层 while (cur <= r),内层 for (k...)。看起来像 O(QN)O(Q \cdot N)
        • 其实不是。请注意 ans++cur++ 发生的条件:当前 cur 无法再进行任何跳跃。这意味着 nxt[cur] > r
        • 一旦我们保留了数值为 VV 的牌,说明该数值下次出现的位置在 RR 之外。因此,在剩下的区间里,我们再也不会遇到能被保留的数值 VV(因为它下次出现都没了,更别说再下次)。
        • 因为 Ai13A_i \le 13,所以 ans++ 最多执行 13 次。
        • 外层 while 循环最多执行约 14 次。总复杂度是 O(Q×13×logN)O(Q \times 13 \times \log N),完全可以接受。
      3. 边界处理

        • 数组大小开到 15005,防止越界。
        • pos 数组初始化为 INF,方便判断不存在的情况。
        • 引入 N+1 虚拟节点表示结束,简化了代码逻辑(不需要到处判断 cur > n)。
      4. 优化建议

        • 当前代码已经是最优解法之一。
        • 如果 NN 很大但 AiA_i 依然很小,这个算法依然有效。
        • 如果 AiA_i 很大(如 10910^9),则复杂度会退化,需要改用更高级的数据结构(如主席树维护区间),但这超出了本题范围。
      • 0
        @ 2025-12-15 16:42:53

        你好!作为一名细心的OI教练,很高兴能和你一起探讨这道有趣的题目 P10264 [GESP202403 八级] 接竹竿。

        这道题是一道非常经典的结合贪心策略与倍增优化的题目。特别是看到了 Ai13A_i \le 13 这个极小的数据范围,你的“信息学直觉”雷达应该疯狂作响!

        下面我们按照教学流程,一步步拆解思路。


        一、 预备知识点

        在解决这道题之前,你需要掌握以下工具:

        1. 贪心思想:理解为什么对于某个位置的牌,我们只需要关注它“下一次”出现的位置。
        2. 预处理:如何快速找到每个位置后面第一个数值相同的元素(Next数组)。
        3. ST表 / 倍增算法(Doubling):用于在链表或树上快速跳跃 2k2^k 步,以及在倍增过程中维护区间最大值(RMQ思想)。
        4. 鸽巢原理(抽屉原理):用于分析为什么特定的操作次数很少,从而保证时间复杂度。

        二、 思路引导与草稿纸推演

        假设我们在黑板前,现在请拿出草稿纸,我们画图来模拟一下。

        1. 理解消除规则(贪心策略)

        提问:假设你现在手里拿着一张牌,点数是 XX,放在了队列末尾。什么时候这张牌会被消除? 分析

        • 题目说:“若放入之前这列牌中已有与这张牌点数相同的牌……取出……之间的所有牌”。
        • 这就意味着,对于下标 ii 的这张牌(点数 AiA_i),如果它后面还有一张点数为 AiA_i 的牌在下标 jj 处 (j>ij > i)。
        • 只要我们在处理的过程中,能够到达 jj 这个位置(即 iijj 中间的部分被消掉了,或者 ii 就是当前的栈底),那么 iijj 就会配对,并且把 [i,j][i, j]这一整段全部消掉!
        • 消掉之后,游戏的“光标”会直接跳到 j+1j+1

        推论: 对于任意位置 ii,我们只需要找到它后面第一个点数相同的位置,记为 nxt[i]nxt[i]

        • 如果 nxt[i]nxt[i] 在查询的右端点 RR 之内(nxt[i]Rnxt[i] \le R):一旦我们遇到了 ii,且 ii 没有被前面的操作消掉,那么 ii 一定会和 nxt[i]nxt[i] 配对,导致 [i,nxt[i]][i, nxt[i]] 这一段全部消失。下一步我们处理 nxt[i]+1nxt[i] + 1
        • 如果 nxt[i]nxt[i]RR 之外(nxt[i]>Rnxt[i] > R 或不存在):说明在当前游戏范围内,ii 找不到配对对象。那么 ii 就会幸存下来,留在队列里。下一步我们处理 i+1i+1

        2. 草稿纸上的模拟(寻找规律)

        设序列 A=[1,2,3,2,1,3]A = [1, 2, 3, 2, 1, 3],查询区间 [1,6][1, 6]。 先预处理 nxtnxt 数组(找后面第一个相同的数):

        • i=1(1)nxt=5(1)i=1(1) \to nxt=5(1)
        • i=2(2)nxt=4(2)i=2(2) \to nxt=4(2)
        • i=3(3)nxt=6(3)i=3(3) \to nxt=6(3)
        • i=4(2)nxt=i=4(2) \to nxt=\infty
        • ...

        模拟过程

        1. cur=1cur = 1 开始。
        2. nxt[1]=5nxt[1] = 5。因为 565 \le 6(在区间内),所以 1155 必定配对消除。
          • 跳跃:直接跳到 5+1=65 + 1 = 6
        3. 现在 cur=6cur = 6。看 A6=3A_6=3,它的 nxtnxt 在后面没有了(\infty)。
          • 无法消除:说明 A6A_6 会留在队列里。
          • 计数:答案 +1+1
          • 步进curcur 变成 6+1=76+1=7
        4. 7>67 > 6,结束。答案是 1。

        3. 遇到瓶颈(时间复杂度分析)

        如果每次询问我们都这样一步步跳:

        • 最好情况:一下子跳很远。
        • 最坏情况:序列是 1 2 3 4 5 ...,每个数的 nxtnxt 都在很远或者不存在。你需要一步步 cur++
        • 单次询问最坏 O(N)O(N),总复杂度 $O(QN) \approx 1.5 \times 10^4 \times 1.5 \times 10^4 \approx 2.25 \times 10^8$。
        • 虽然 TT 比较小,但这还是有点危险,我们需要优化!

        4. 优化:倍增“跳跃”(金牌考点)

        我们发现,“消除”操作就是一次跳跃:从 ii 跳到 nxt[i]+1nxt[i] + 1。 我们可以预处理倍增数组,帮我们快速跳过那些“能消除”的段。

        定义状态

        • f[i][k]f[i][k]:从位置 ii 开始,连续进行 2k2^k 次“消除跳跃”,到达的位置。
          • f[i][0]=nxt[i]+1f[i][0] = nxt[i] + 1
          • f[i][k]=f[f[i][k1]][k1]f[i][k] = f[ f[i][k-1] ][k-1]

        关键问题: 跳跃是有限制的!只有当跳跃的终点(或者说跳跃所依赖的那个 nxtnxt 值)在查询区间 RR 以内时,才能跳。 如果我们要跳 2k2^k 步,这路径上经过的所有“配对点”都必须 R\le R

        增加维护信息

        • mx[i][k]mx[i][k]:从 ii 开始跳 2k2^k 步的路径上,涉及到的最大的 nxtnxt 值是多少。
          • 如果 mx[i][k]Rmx[i][k] \le R,说明这 2k2^k 步跳跃在当前询问中是合法的。

        5. 最终算法流程(包含复杂度分析)

        对于查询 [L,R][L, R]

        1. 当前位置 cur=Lcur = L
        2. 尝试倍增跳跃
          • 从大到小枚举 kk(比如从 15 到 0)。
          • 检查:如果 f[cur][k]f[cur][k] 存在,且路径上的最大 nxtnxtmx[cur][k]Rmx[cur][k] \le R
          • 执行跳跃:cur=f[cur][k]cur = f[cur][k]
          • (这一步把能消的全部快速消掉了)
        3. 处理无法跳跃的情况
          • 此时,curcur 位置的 nxt[cur]>Rnxt[cur] > R 或者不存在。
          • 这意味着 AcurA_{cur} 这个数在 [cur,R][cur, R] 范围内找不到配对,它一定会幸存
          • 操作:答案 +1+1,然后强制往后移一格 cur=cur+1cur = cur + 1
        4. 回到步骤 2,直到 cur>Rcur > R

        为什么这样就快了?(核心亮点) 你可能会问:如果步骤 3 一直发生怎么办?比如 1 2 3 4 5 这种。

        • 请看数据范围:Ai13A_i \le 13
        • 如果我们在步骤 3 “幸存”了一个数,比如 11,那么在剩下的区间里,如果再出现 11,它的 nxtnxt 一定在 RR 之外(否则刚才在步骤 2 就会跳过去)。
        • 根据鸽巢原理,在 RR 范围之内,最多只能有 13 个不同的数值找不到配对!
        • 所以,步骤 3 最多只会执行 13次
        • 总复杂度:O(Q×(13+logN))O(Q \times (13 + \log N))。这是一个非常优秀的复杂度。

        三、 总结:读题关键词与解题模式

        在阅读此类题目时,注意捕捉以下关键词:

        1. “取出……之间的所有牌” \rightarrow 区间消除 / 括号匹配模型。暗示了跳跃关系。
        2. Ai13A_i \le 13 \rightarrow 极小值域。这是极其强烈的暗示,说明复杂度可能与值域有关(例如状压、或者本题中的幸存元素个数上限)。
        3. 多次询问区间 [l,r][l, r] \rightarrow 离线处理 或 倍增/线段树在线查询。本题因为有 RR 的限制,倍增是很好的选择。

        四、 最后的提示

        • 构建 nxtnxt 数组时,可以倒序遍历,利用一个数组 pos[value] 记录某个值最近一次出现的下标。
        • 倍增数组的边界条件要处理好(跳出 NN 以外的情况)。
        • 注意 mx[i][k]mx[i][k] 的传递,是取 max(mx[i][k-1], mx[f[i][k-1]][k-1])

        现在,试着在草稿纸上把倍增数组的转移方程写完整,然后就可以开始写代码了!加油!

        • 1

        信息

        ID
        14753
        时间
        1000ms
        内存
        32MiB
        难度
        6
        标签
        递交数
        2
        已通过
        1
        上传者