bzoj3809. Gty的二逼妹子序列

知识点: 莫队,分块

原题面:darkbzoj Luogu 双倍经验


题意简述

给定一长度为 \(n\) 的数列 \(a\)\(m\) 次询问。
每次询问给定参数 \(l,r,a,b\),求区间 \([l,r]\) 内权值 \(\in [a,b]\) 的数的种类数。
\(1\le a_i\le n\le 10^5\)\(1\le m\le 10^6\)


分析题意

发现莫队比较便于维护种类数,套一个莫队消去区间的限制。
考虑值域的限制,权值线段树进行维护。
单次 修改/查询 复杂度均为 \(O(\log n)\)
设块大小为 \(\dfrac{n}{\sqrt{m}}\),总复杂度为 \(O(n\sqrt{m}\log n + m\log n)\)


发现修改和查询的次数并不平均,而线段树查询修改的复杂度是平均的。
考虑能否替换成另一种 修改复杂度较小,查询复杂度较大的数据结构。
想到直接使用数组进行O1修改On查询

只有单点修改,考虑对值域分块,维护块内不同的数的个数,可在莫队左右端点移动顺便维护。
查询时,在查询值域内的完整块直接统计答案,不完整块暴力查询。
设块大小为 \(\dfrac{n}{\sqrt{m}}\),单次修改复杂度 \(O(1)\),查询复杂度 \(\sqrt{n}\),总复杂度为 \(O(n\sqrt{m} +m\sqrt{n})\)


爆零小技巧


代码实现

//知识点:莫队,分块 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 1e5 + 10;
const int kMaxm = 1e6 + 10;
const int kMaxSqrtn = 320 + 10;
//=============================================================
struct Que {
  int l, r, a, b, id;
} q[kMaxm];
int n, m, a[kMaxn];
int block_size, block_num, L[kMaxn], R[kMaxn], bel[kMaxn];
int nowl = 1, nowr, cnt[kMaxn], sum[kMaxSqrtn];
int ans[kMaxm];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void GetMin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
bool CompareQuery(Que fir, Que sec) {
  if (bel[fir.l] != bel[sec.l]) return bel[fir.l] < bel[sec.l];
  return fir.r < sec.r;
}
void Prepare() {
  n = read(), m = read();
  for (int i = 1; i <= n; ++ i) a[i] = read();
  for (int i = 1; i <= m; ++ i) {
    q[i] = (Que) {read(), read(), read(), read(), i};
  }
  block_size = (int) sqrt(n), block_num = n / block_size;
  for (int i = 1; i <= block_num; ++ i) {
    L[i] = (i - 1) * block_size + 1;
    R[i] = i * block_size;
  }
  if (R[block_num] < n) {
    L[++ block_num] = R[block_num - 1] + 1;
    R[block_num] = n;
  } 
  
  for (int j = 1; j <= block_num; ++ j) {
    for (int i = L[j]; i <= R[j]; ++ i) {
      bel[i] = j;
    }
  }
  std :: sort(q + 1, q + m + 1, CompareQuery);
}

int Query(int l_, int r_) {
  int ret = 0;
  if (bel[l_] == bel[r_]) {
    for (int i = l_; i <= r_; ++ i) ret += (cnt[i] > 0);
    return ret;
  }
  for (int i = bel[l_] + 1; i <= bel[r_] - 1; ++ i) {
    ret += sum[i];
  }
  for (int i = l_; i <= R[bel[l_]]; ++ i) ret += (cnt[i] > 0);
  for (int i = L[bel[r_]]; i <= r_; ++ i) ret += (cnt[i] > 0);
  return ret;
}
void Delete(int now_) {
  cnt[a[now_]] --;
  if (! cnt[a[now_]]) sum[bel[a[now_]]] --;
}
void Add(int now_) {
  if (! cnt[a[now_]]) sum[bel[a[now_]]] ++;
  cnt[a[now_]] ++;
}
//=============================================================
int main() {
  Prepare();
  for (int i = 1; i <= m; ++ i) {
    int l = q[i].l, r = q[i].r, a = q[i].a, b = q[i].b, id = q[i].id;
    while (nowl < l) Delete(nowl), nowl ++;
    while (nowl > l) nowl --, Add(nowl);
    while (nowr > r) Delete(nowr), nowr --;
    while (nowr < r) nowr ++, Add(nowr);
    ans[q[i].id] = Query(a, b);
  }
  for (int i = 1; i <= m; ++ i) printf("%d\n", ans[i]);
  return 0;
}
posted @ 2020-09-03 22:30  Luckyblock  阅读(171)  评论(0编辑  收藏  举报