1 条题解

  • 0
    @ 2025-8-24 23:16:11

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Kagamine_Rin
    主页:tiy7zto3|互关原则:只要私信就会用小号回关,一定要发,列表看不到的|可恶洛谷把我7√→6√|灰名可能会暂时取关,若被杀请私信(话说谁JC我,封面怎么是wssb,既然只是一个挂名账号那就不改了)

    搬运于2025-08-24 23:16:11,当前版本为作者最后更新于2025-05-21 19:44:37,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    这题真的是毒瘤啊……

    注:本题解提供 O(nnlogn)\mathcal{O}(n\sqrt {n\log n}) 的解法,不是正解,如果要学习 O(nn)\mathcal{O}(n\sqrt n) 的正解请看别的题解。

    简要题意

    给定一个数组 {a1,,an}\{a_1, \dots, a_n\},有 qq 次询问(l,r,L,R,p,xl, r, L, R, p, x),并回答 alara_l\sim a_r 中有多少个 aia_i 满足 LaiRL \le a_i \le Raimodp=xa_i \bmod p = x

    强制在线。

    看到 aimodp=xa_i \bmod p = x 就可以想到使用根号分治。

    考虑将值域作为下标,存储该数值在 aa 中的位置

    std::vector<int> vtop[V];
    for (int i = 1; i <= n; ++ i) cin >> a[i];
    for (int i = 1; i <= n; ++ i) vtop[a[i]].emplace_back(i);
    

    设阈值为 BB(一般另 B=VB = \sqrt VVV 为值域)。

    当模数 p>Bp > B 时,最多只要枚举 VP\frac{V}{P} 个可能的 aia_i 满足条件,所以直接暴力。我们可以枚举每一个 aia_i,在 aia_i 的所有可能的下标中用二分找到左端点(lposlpos右端点(rposrpos,答案即为 rposlpos+1rpos - lpos + 1,代码如下:

    if (p > B) {
      for (int i = (L - x + p - 1) / p * p + x; i <= R; i += p)
        ans += (upper_bound(vtop[i].begin(), vtop[i].end(), r) - vtop[i].begin()) - (lower_bound(vtop[i].begin(), vtop[i].end(), l) - vtop[i].begin());
        // 这里没有 +1 是因为 upper_bound 会找到 rpos 右边一格,所以 upper_bound 这里返回的是 rpos + 1
      cout << ans << "\n";
    }
    

    当模数 pBp \le B 时,我们可以对下标进行分块。创建一个 vector 数组 did,p,xd_{id, p, x} 预处理出 aidlaidra_{id_l}\sim a_{id_r}idlid_lidrid_r 分别表示该块的左端点和右端点)中满足 aimodp=xa_i \bmod p = x 的所有 aia_i 组成的序列,代码如下:

    for (int i = 1; i <= n; ++ i) {
      const int bl_id = (i - 1) / Block_N + 1; // 该下标对应的块编号
      for (int p = 1; p <= Block_V; ++ p)
        d[bl_id][p][a[i] % p].emplace_back(a[i]);
    }
    

    然后不能忘记排序:

    for (int i = 0; i <= bl(n); ++ i)
      for (int p = 0; p <= Block_V; ++ p)
        for (int x = 0; x <= Block_V; ++ x)
          sort(d[i][p][x].begin(), d[i][p][x].end());
    

    在被询问时就很显然了:

    else { // if (p <= B)
      if (bl(l) == bl(r)) { // 左端点和右端点在同一个块,特殊处理
        for (int i = l; i <= r; ++ i) ans += L <= a[i] && a[i] <= R && a[i] % p == x;
        cout << ans << "\n";
      } else {
        while ((l - 1) % Block_N) L <= a[l] && a[l] <= R && a[l] % p == x && ++ ans, ++ l;
        while (r % Block_N)       L <= a[r] && a[r] <= R && a[r] % p == x && ++ ans, -- r;
        // ↑不是完整的块,暴力处理
        for (int i = bl(l); i <= bl(r); ++ i)
          ans += (upper_bound(d[i][p][x].begin(), d[i][p][x].end(), R) - d[i][p][x].begin()) - (lower_bound(d[i][p][x].begin(), d[i][p][x].end(), L) - d[i][p][x].begin());
        // 完整的块,直接相加(没有 +1 的原因和上面同理)
        cout << ans << "\n";
      }
    }
    

    然后我们终于写完了这份代码,并获得了 10 分的高分!

    %:import "functional"
    %:import "algorithm"
    %:import "iostream"
    %:import "string.h"
    %:import "queue"
    %:define For(a, b, c) for (int a = b, a##END = c; a <= a##END; ++ a)
    %:define bl(x) (((x) - 1) / Block_N + 1)
    %:define bl_l(b) ((b) * Block_N - Block_N + 1)
    %:define bl_r(b) ((b) * Block_N)
    const int N = 1e5 + 7, V = 2e5 + 7, Block_V = 100, Block_N = 316; // Block_V 就是上文写道的 B,这里用于和 Block_N 进行区分
    
    int a[N];
    std::vector<int> vtop[V];
    std::vector<int> d[318][Block_V + 1][Block_V + 1];
    main() {
      using namespace std;
      ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
      int n, type, lastans = 0; cin >> n >> type;
    
      for (int i = 1; i <= n; ++ i) {
        cin >> a[i], vtop[a[i]].emplace_back(i);
        const int bl_id = (i - 1) / Block_N + 1;
        for (int p = 1; p <= Block_V; ++ p)
          d[bl_id][p][a[i] % p].emplace_back(a[i]);
      }
      for (int i = 0; i <= Block_N; ++ i)
        for (int p = 0; p <= Block_V; ++ p)
          for (int x = 0; x <= Block_V; ++ x)
            sort(d[i][p][x].begin(), d[i][p][x].end());
      int T; cin >> T;
      while (T --) {
        int l, r, L, R, p, x, ans = 0; cin >> l >> r >> L >> R >> p >> x;
        if (type) l ^= lastans, r ^= lastans, L ^= lastans, R ^= lastans, p ^= lastans, x ^= lastans;
        if (p > Block_V)
          for (int i = (L - x + p - 1) / p * p + x; i <= R; i += p)
            ans += (upper_bound(vtop[i].begin(), vtop[i].end(), r) - vtop[i].begin()) - (lower_bound(vtop[i].begin(), vtop[i].end(), l) - vtop[i].begin());
        else {
          if (bl(l) == bl(r)) {
            for (int i = l; i <= r; ++ i) ans += L <= a[i] && a[i] <= R && a[i] % p == x;//, fprintf(stderr, "%d, ", a[i] % p);
            goto opt;
          }
          while ((l - 1) % Block_N) ans += L <= a[l] && a[l] <= R && a[l] % p == x, ++ l;
          while (r % Block_N)       ans += L <= a[r] && a[r] <= R && a[r] % p == x, -- r;
          for (int i = bl(l), iEND = bl(r); i <= iEND; ++ i)
            ans += (upper_bound(d[i][p][x].begin(), d[i][p][x].end(), R) - d[i][p][x].begin()) - (lower_bound(d[i][p][x].begin(), d[i][p][x].end(), L) - d[i][p][x].begin());
        }
        opt: cout << (lastans = ans) << "\n";
      }
    }
    

    为什么?

    因为这道题时间和空间卡得很紧,连正解都要卡常,更何况非正解呢?

    以下是我们需要注意的地方:

    1. 由于 pBp\le B 的部分常数比较大,所以不能使用传统的 V\sqrt V 作为阈值,而要使用更小的数,比如 100。
    2. 询问区间(LLRR)不一定是从 1V1\sim V 的,事实上可能长度远小于 VV,所以我们还可以加上判断 if (p > B || (R - ((L - x + p - 1) / p * p + x)) <= 32768)
    3. d[i][j][k].size() <= 1 时,根本不需要排序,排序就是浪费时间,所以我们要在排序前写上:if (d[i][j][k].size() > 1)
    4. 对于 for (int p = 1; p <= B; ++ p) 这里,由于 p[1,100]p\in[1,100],我们完全可以手写 100 遍代替一个 for 循环。

    经过无数次的尝试,我们终于得到了以下 AC 代码,并愉快地切掉了一道黑题(Record):

    #import "algorithm" // 码风过丑,勿喷
    #import "iostream"
    #import "vector"
    #define For(a, b, c) for (int a = b, a##END = c; a <= a##END; ++ a)
    #define bl(x) (((x) - 1) / Block_N + 1)
    #define bl_l(b) ((b) * Block_N - Block_N + 1)
    #define bl_r(b) ((b) * Block_N)
    const int N = 1e5 + 7, V = 2e5 + 7, Block_V = 100, Block_N = 750;
    int a[N];
    std::vector<int> vtop[V];
    std::vector<int> d[bl(100000) + 2][Block_V + 1][Block_V + 1];
    main() {
      using namespace std;
      ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
      int n, type; cin >> n >> type;
    
      for (int i = 1; i <= n; ++ i) cin >> a[i];
      for (int i = 1; i <= n; ++ i) vtop[a[i]].emplace_back(i);
      for (int i = 1; i <= n; ++ i) {
        const int bl_id = (i - 1) / Block_N + 1;
    #define op(p) d[bl_id][p][a[i] % p].emplace_back(a[i])
        op(1), op(2), op(3), op(4), op(5), op(6), op(7), op(8), op(9), op(10), op(11), op(12), op(13), op(14), op(15), op(16), op(17), op(18), op(19), op(20), op(21), op(22), op(23), op(24), op(25), op(26), op(27), op(28), op(29), op(30), op(31), op(32), op(33), op(34), op(35), op(36), op(37), op(38), op(39), op(40), op(41), op(42), op(43), op(44), op(45), op(46), op(47), op(48), op(49), op(50), op(51), op(52), op(53), op(54), op(55), op(56), op(57), op(58), op(59), op(60), op(61), op(62), op(63), op(64), op(65), op(66), op(67), op(68), op(69), op(70), op(71), op(72), op(73), op(74), op(75), op(76), op(77), op(78), op(79), op(80), op(81), op(82), op(83), op(84), op(85), op(86), op(87), op(88), op(89), op(90), op(91), op(92), op(93), op(94), op(95), op(96), op(97), op(98), op(99), op(100);
      }
      for (int i = 0; i <= bl(n); ++ i)
        for (int p = 0; p <= Block_V; ++ p)
          for (int x = 0; x <= Block_V; ++ x)
            if (d[i][p][x].size() > 1) sort(d[i][p][x].begin(), d[i][p][x].end());
      int T, ans = 0; cin >> T;
      while (T --) {
        int l, r, L, R, p, x; cin >> l >> r >> L >> R >> p >> x;
        type && (l ^= ans, r ^= ans, L ^= ans, R ^= ans, p ^= ans, x ^= ans), ans = 0;
        if (p > Block_V || (R - ((L - x + p - 1) / p * p + x)) <= 32768) {
          for (int i = (L - x + p - 1) / p * p + x; i <= R; i += p)
            ans += (upper_bound(vtop[i].begin(), vtop[i].end(), r) - vtop[i].begin()) - (lower_bound(vtop[i].begin(), vtop[i].end(), l) - vtop[i].begin());
          cout << ans << "\n";
        }
        else {
          if (bl(l) == bl(r)) {
            for (int i = l; i <= r; ++ i) ans += L <= a[i] && a[i] <= R && a[i] % p == x;//, fprintf(stderr, "%d, ", a[i] % p);
            cout << ans << "\n";
          } else {
            while ((l - 1) % Block_N) L <= a[l] && a[l] <= R && a[l] % p == x && ++ ans, ++ l;
            while (r % Block_N)       L <= a[r] && a[r] <= R && a[r] % p == x && ++ ans, -- r;
            for (int i = bl(l), iEND = bl(r); i <= iEND; ++ i)
              ans += (upper_bound(d[i][p][x].begin(), d[i][p][x].end(), R) - d[i][p][x].begin()) - (lower_bound(d[i][p][x].begin(), d[i][p][x].end(), L) - d[i][p][x].begin());
            cout << ans << "\n";
          }
        }
      }
    }
    

    卡常不易,点个赞再走吧~

    对了,请不要抄题解,因为你抄了也过不了,就是浪费时间(别问我怎么知道的)

    • 1

    信息

    ID
    11702
    时间
    1500ms
    内存
    1024MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者