【ACWing】2521. 数颜色

题目地址:

https://www.acwing.com/problem/content/description/2523/

墨墨购买了一套 N N N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令:
Q L R代表询问你从第 L L L支画笔到第 R R R支画笔中共有几种不同颜色的画笔。
R P Col把第 P P P支画笔替换为颜色Col。
为了满足墨墨的要求,你知道你需要干什么了吗?

输入格式:
1 1 1行两个整数 N , M N,M N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
2 2 2 N N N个整数,分别代表初始画笔排中第 i i i支画笔的颜色。
3 3 3行到第 2 + M 2+M 2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。

输出格式:
对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第 L L L支画笔到第 R R R支画笔中共有几种不同颜色的画笔。

数据范围:
1 ≤ N , M ≤ 10000 1≤N,M≤10000 1N,M10000
修改操作不多于 1000 1000 1000
所有的输入数据中出现的所有整数均大于等于 1 1 1且不超过 1 0 6 10^6 106

带修改莫队。莫队基本思想参考https://blog.csdn.net/qq_46105170/article/details/125827776。本题里还有修改操作,所以我们挪动指针的时候,除了挪动区间端点的两个指针以外,还需要挪动时间指针。首先将每个询问和修改都存起来,存修改的时候,数组下标即为此次修改的时间戳;这样对于每个询问操作,除了记录该询问的编号、左右端点之外,还要把时间戳记录下来。

接下来类似莫队的操作,对询问排序,先按左端点所在分块的编号递增排,左端点所在块相同则按右端点所在分块的编号递增排,如果还相同,为了时间指针移动次数尽量少,按时间递增排。

接下来处理每个询问,开三个指针 i , j , t i, j, t i,j,t分别代表左右端点的指针和时间指针。一开始 t = 0 t=0 t=0。先按基础莫队的办法,将 i i i j j j移动到左右端点,同时更新计数。端点指针移动到位之后,开始移动时间指针。如果 t t t小于询问时间,那么我们顺着时间增长的方向修改数组,并且如果修改的位置是区间内的,还需要更新答案;如果 t t t大于询问时间,那么我们顺着时间逆流的方向修改数组,同样的,如果修改的位置是区间内的,还需要更新答案。为了方便操作,在修改的时候,我们直接将被修改的数和修改操作里记录的数做交换,这样时间顺流的时候如果修改是 x → y x\to y xy,那么时间逆流的时候修改就是 y → x y\to x yx,这样恰好满足需求。

考虑指针移动的总次数。设块的大小为 a a a,则 j j j指针移动次数为 O ( a m + 2 n ) O(am+2n) O(am+2n) t t t指针移动次数为 O ( n 2 2 a 2 T ) O(\frac{n^2}{2a^2}T) O(2a2n2T) T T T为时间戳最大值,即修改总次数(固定 i i i j j j所在块之后, t t t最多移动 T T T次,而所在块的方案数大概是 n 2 2 a 2 \frac{n^2}{2a^2} 2a2n2)。而对于 i i i指针, j j j块内移动的时候,其最多移动 O ( n ) O(n) O(n),总共 O ( n 2 a ) O(\frac{n^2}{a}) O(an2),当 j j j跨块的时候,总次数 O ( n a m ) O(\frac{n}{a}m) O(anm),所以总共是 O ( a m + 2 n + n 2 2 a 2 T + n 2 a + n a m ) O(am+2n+\frac{n^2}{2a^2}T+\frac{n^2}{a}+\frac{n}{a}m) O(am+2n+2a2n2T+an2+anm)。如果分块每块大小 n \sqrt n n 的话,每次询问平均需要 O ( n + T ) O(\sqrt n+T) O(n +T)(注意本题中 n ∼ m n\sim m nm)。

代码如下:

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 1e4 + 10, S = 1e6 + 10;
int n, m, len, mc, mq;
int w[N], cnt[S], res[N], ans;
struct Query {
  int id, l, r, t;
} q[N];
// c[t]代表t这个时间戳,做了将w[p]变成c的操作。t从1开始
struct Modify {
  int p, c;
} c[N];

void add(int x) {
  if (!cnt[x]) ans++;
  cnt[x]++;
}

void del(int x) {
  cnt[x]--;
  if (!cnt[x]) ans--;
}

int main() {
  scanf("%d%d", &n, &m);
  for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
  for (int i = 0; i < m; i++) {
    char op[2];
    int a, b;
    scanf("%s%d%d", op, &a, &b);
    if (*op == 'Q') mq++, q[mq] = {mq, a, b, mc};
    else c[++mc] = {a, b};
  }
  len = sqrt(n);

  sort(q + 1, q + 1 + mq, [&](Query& a, Query& b) {
    int al = a.l / len, bl = b.l / len;
    if (al != bl) return al < bl;
    int ar = a.r / len, br = b.r / len;
    // 单调优化(玄学优化)
    if (ar != br) if (al & 1) return ar < br; else return ar > br;
    return a.t < b.t;
  });

  for (int k = 1, i = 0, j = 1, t = 0; k <= mq; k++) {
    int id = q[k].id, l = q[k].l, r = q[k].r, tm = q[k].t;
    while (i < r) add(w[++i]);
    while (i > r) del(w[i--]);
    while (j < l) del(w[j++]);
    while (j > l) add(w[--j]);

    while (t < tm) {
      t++;
      if (l <= c[t].p && c[t].p <= r) {
        del(w[c[t].p]);
        add(c[t].c);
      }
      swap(w[c[t].p], c[t].c);
    }
    while (t > tm) {
      if (l <= c[t].p && c[t].p <= r) {
        del(w[c[t].p]);
        add(c[t].c);
      }
      swap(w[c[t].p], c[t].c);
      t--;
    }

    res[id] = ans;
  }

  for (int i = 1; i <= mq; i++) printf("%d\n", res[i]);
}

预处理时间复杂度 O ( m q log ⁡ m q ) O(m_q\log m_q) O(mqlogmq) m q m_q mq为询问次数,每次询问时间 O ( n + T ) O(\sqrt n+T) O(n +T) T T T为最大时间戳,空间 O ( m + S ) O(m+S) O(m+S) S S S为不同颜色数量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值