数据结构题锦——莫队

 

zoto【来源:2021杭电多校第一场的第10题】

题目:

Problem Description
You are given an array fx.
For each i(1<=i<=n) , we use a point (i,fx[i]) in XoY coordinate plane to described it.
You are asked to answer m queries, in each query there will be a rectangle and you need to count how many different y-cooordinate (of the points mentioned above) in the queried rectangle.
 
Input
The first line contains an integer T(1<=T<=5) representing the number of test cases.
For each test case , there are two integers n,m(1<=n<=100000,1<=m<=100000) in the first line.
Then one line contains n integers fx[i](0<=fx[i]<=100000)
Each of the next m lines contain four integers x0,y0,x1,y1(1<=x0<=x1<=n,0<=y0<=y1<=100000) which means matrix's lower-leftmost cell is (x0,y0) and upper-rightest cell is (x1,y1).
 
Output
For each test case print a single integer in a new line.
 
Sample Input
1 4 2 1 0 3 1 1 0 4 3 1 0 4 2
 
Sample Output
3 2
 
Source

分析:

> 题目:
> T组测试数据, T大小为5:
> 给定n个坐标,横坐标为从1到n,对应纵坐标为fx[i],共有m此询问,其中n,m,fx大小都是1e5
> 每次询问,查询一个以(x0,y0)为左下角,以(x1,y1)为右上角的矩形面积内,有多少个不同的fx

> 解决:
> T是T,m是1e5,那么,我们每次的查询操作的时间复杂度需要降到O(logn)才能合法,而且,只有查询没有修改,基础莫队便可以解决次题。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, len;
int w[N], cnt[N], sum[N], ans[N];
struct Query{
  int l, r, L, R, id;
}q[N];
int get(int x)
{
  return x / len;
}
bool cmp(const Query& a, const Query& b)
{
  int x = get(a.l), y = get(b.l);
  if (x != y) return x < y;
  return a.r < b.r;
  // if (x == y) return a.r < b.r;
  // return (x & 1) ? x > y : x < y;
}
void add(int x)
{
  // printf("del: x = %d, cnt[x] = %d\n", x, cnt[x]);
  //sum计算块内的节点数量,我们对y轴分块,计算的是fx的分块内的值
  if (!cnt[x]) sum[get(x)] ++;
  cnt[x] ++;
}
void del(int x)
{
  // printf("del: x = %d, cnt[x] = %d\n", x, cnt[x]);
  cnt[x] --;
  if (!cnt[x]) sum[get(x)] --;
}
int calc(int y)
{
  //计算y轴,从0到y的个数
  int res = 0;
  //sum代表y轴分块val的数量,res先求的是前缀和
  for (int i = 0; i < get(y); i ++ ) res += sum[i];
  //最上面的一点,这些不再整块内,暴力计算
  for (int i = get(y) * len; i <= y; i ++ ) res += (cnt[i] >= 1);
  return res;
}
void solve()
{
  memset(cnt, 0, sizeof cnt);
  memset(sum, 0, sizeof sum);
  scanf("%d%d", &n, &m);
  // len = sqrt(n);
  len = 131;
  for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
  for (int i = 0; i < m; i ++ )
  {
    int l, L, r, R; scanf("%d%d%d%d", &l, &L, &r, &R);
    q[i] = {l, r, L, R, i};
  }
  sort(q, q + m, cmp);
  for (int i = 0, l = 1, r = 0; i < m; i ++ )
  {
    // printf("q[i].l = %d, bottom = %d, q[i].r = %d, top = %d\n", q[i].l, q[i].L, q[i].r, q[i].R);
    // printf("del l ++ \n");
    while (l < q[i].l) del(w[l ++ ]);
    // printf("add -- l \n");
    while (l > q[i].l) add(w[-- l ]);
    // printf("add ++ r \n");
    while (r < q[i].r) add(w[++ r ]);
    // printf("del r -- \n");
    while (r > q[i].r) del(w[r -- ]);
    ans[q[i].id] = calc(q[i].R) - calc(q[i].L - 1);
  }
  for (int i = 0; i < m; i ++ ) printf("%d\n", ans[i]);
}
int main()
{
  int T = 1;
  cin >> T;
  while (T -- )
  {
    solve();
  }
  return 0;
}

  

2492. HH的项链 - AcWing题库

题目:

HH 有一串由各种漂亮的贝壳组成的项链。

HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。

HH 不断地收集新的贝壳,因此他的项链变得越来越长。

有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?

这个问题很难回答,因为项链实在是太长了。

于是,他只好求助睿智的你,来解决这个问题。

输入格式

第一行:一个整数 NN,表示项链的长度。

第二行:NN 个整数,表示依次表示项链中贝壳的编号(编号为 00 到 10000001000000 之间的整数)。

第三行:一个整数 MM,表示 HH 询问的个数。

接下来 MM 行:每行两个整数,LL 和 RR,表示询问的区间。

输出格式

MM 行,每行一个整数,依次表示询问对应的答案。

数据范围

1N500001≤N≤50000,
1M2×1051≤M≤2×105,
1LRN1≤L≤R≤N

输入样例:

6
1 2 3 4 3 5
3
1 2
3 5
2 6

输出样例:

2
2
4

分析:

题目:

给定一个长度为n的序列w,n长度为5e4,w大小为5e4,对此序列进行m次查询,m大小为2e5,每次查询询问一个区间内有多少个不同的数字。

解决:

这时离线询问操作,可以使用基础莫队解决此题。时间复杂度为O(nsqrt(n))

莫队算法将

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 50005, M = 200005, S = 1000010;
int n, m, T, a[N];
struct Q{
  int l, r, x, y, i;
  inline Q() {}
  inline Q(int l, int r, int i) : l(l), r(r), i(i) {x = l / T, y = r;}
  inline friend bool operator< (const Q &a, const Q &b){
    return a.x == b.x ? (a.x & 1) ? a.y < b.y : a.y > b.y : a.x < b.x;
  }
}q[M];
int ans[M], cnt[S];
void add(int x, int& now)
{
  if (!cnt[x]) now ++;
  cnt[x] ++;
}
void del(int x, int& now)
{
  cnt[x] --;
  if (!cnt[x]) now --;
}
int main()
{
  scanf("%d", &n);
  for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
  scanf("%d", &m);
  T = sqrt((double)n * n / m);
  for (int i = 0; i < m; i ++ )
  {
    int l, r; scanf("%d%d", &l, &r);
    q[i] = {l, r, i};
  }
  sort(q, q + m);
  for (int i = 0; i < m; i ++ )
    printf("[l = %d, r = %d]\n", q[i].l, q[i].r);
  for (int i = 0, l = 1, r = 0, now = 0; i < m; i ++ )
  {
    while (l > q[i].l) add(a[ -- l], now);
    while (l < q[i].l) del(a[ l ++], now);
    while (r > q[i].r) del(a[ r --], now);
    while (r < q[i].r) add(a[ ++ i], now);
    ans[q[i].i] = now;
  }
  for (int i = 0; i < m; i ++ ) printf("%d\n", ans[i]);
  return 0;
}

  

[P1494 国家集训队]小Z的袜子

题目:

题目描述

upd on 2020.6.10 :更新了时限。

作为一个生活散漫的人,小 Z 每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小 Z 再也无法忍受这恼人的找袜子过程,于是他决定听天由命……

具体来说,小 Z 把这 NN 只袜子从 11 到 NN 编号,然后从编号 LL 到 RR (LL 尽管小 Z 并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小 Z,他有多大的概率抽到两只颜色相同的袜子。当然,小 Z 希望这个概率尽量高,所以他可能会询问多个 (L,R)(L,R) 以方便自己选择。

然而数据中有 L=RL=R 的情况,请特判这种情况,输出0/1

输入格式

输入文件第一行包含两个正整数 NN 和 MM。NN 为袜子的数量,MM 为小 Z 所提的询问的数量。接下来一行包含 NN 个正整数 C_iCi,其中 C_iCi 表示第 ii 只袜子的颜色,相同的颜色用相同的数字表示。再接下来 MM 行,每行两个正整数 L, RL,R 表示一个询问。

输出格式

包含 MM 行,对于每个询问在一行中输出分数 A/BA/B 表示从该询问的区间 [L,R][L,R] 中随机抽出两只袜子颜色相同的概率。若该概率为 00 则输出 0/1,否则输出的 A/BA/B 必须为最简分数。(详见样例)

输入输出样例

输入 #1
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
输出 #1
2/5
0/1
1/1
4/15

说明/提示

30% 的数据中,N,M5000;

60% 的数据中,N,M25000;

100% 的数据中,N,M50000,1L<RN,CiN。

分析:

题目:

给定长度为n的序列w,其中n和w都是50000,给定m次询问,每次询问查找区间内,有两个相同的数字的概率为多少。

解决:

基础莫队。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 50005;
int n, m, len;
int w[N];
int cnt[N];
struct Query{
  int l, r, id;
}q[N];
struct Answer{
  int x, y;
}ans[N], res;
inline int rd()
{
  int x = 0, f = 0; char c = getchar();
  while (!isdigit(c)) if (c == '-') f = 1, c = getchar();
  while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
  return f ? -x : x;
}
inline int get(int x)
{
  return x / len;
}
inline bool cmp(const Query& a, const Query& b)
{
  int x = get(a.l), y = get(b.l);
  if (x == y) return (x & 1) ? a.r < b.r : a.r > b.r;
  return x < y;
}
inline void add(int x)
{
  cnt[x] ++;
  if (cnt[x] > 1) res.x += cnt[x] * (cnt[x] - 1) - (cnt[x] - 1) * (cnt[x] - 2);
}
inline void del(int x)
{
  cnt[x] --;
  if (cnt[x] > 0) res.x -= cnt[x] * (cnt[x] + 1) - cnt[x] * (cnt[x] - 1);
}
inline int gcd(int a, int b)
{
  return b == 0 ? a : gcd(b, a % b);
}
signed main()
{
  // n = rd(), m = rd();
  scanf("%lld%lld", &n, &m);
  len = sqrt(n);
  for (int i = 1; i <= n; i ++ ) scanf("%lld", &w[i]);
   // w[i] = rd();
  for (int i = 0; i < m; i ++ )
  {
    int l, r;
    scanf("%lld%lld", &l, &r);
    // l = rd(), r = rd();
    q[i] = {l, r, i};
  }
  sort(q, q + m, cmp);
  // cout << "-------------" << endl;
  for (int i = 0, l = 1, r = 0; i < m; i ++ )
  {
    if (q[i].l == q[i].r)
    {
      ans[q[i].id] = {0, 1};
      continue;
    }
    while (l < q[i].l) del(w[l ++ ]);
    while (l > q[i].l) add(w[-- l ]);
    while (r < q[i].r) add(w[++ r ]);
    while (r > q[i].r) del(w[r -- ]);
    res.y = (q[i].r - q[i].l + 1) * (q[i].r - q[i].l);
    // printf("%lld\n", res.y);
    int x = res.x, y = res.y;
    int d = gcd(x, y);
    ans[q[i].id] = {x / d, y / d};
    // printf("x = %lld, y = %lld, gcd = %lld\n", x, y, d);
  }
  for (int i = 0; i < m; i ++ ) printf("%lld/%lld\n", ans[i].x, ans[i].y);
  return 0;
}

 

小B的询问

题目:

题目描述

小B 有一个长为 nn 的整数序列 aa,值域为 [1,k]
他一共有 m 个询问,每个询问给定一个区间 [l,r],求:

i=1kci2

其中 ci 表示数字 i在 [l,r] 中的出现次数。
小B请你帮助他回答询问。

输入格式

第一行三个整数 n,m,k。

第二行 n 个整数,表示 小B 的序列。

接下来的 m 行,每行两个整数l,r。

输出格式

输出 m 行,每行一个整数,对应一个询问的答案。

输入输出样例

输入 
6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
输出 #1
6
9
5
2

说明/提示

【数据范围】
对于100% 的数据,1n,m,k5×104。

分析:

基础莫队

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
int n, m, k, len;
int w[N];
int cnt[N], ans[N];
struct Query{
  int l, r, id;
}q[N];
inline int rd()
{
  int x = 0, f = 0; char c = getchar();
  while (!isdigit(c)) if (c == '-') f = 1, c = getchar();
  while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
  return f ? -x : x;
}
int get(int x)
{
  return x / len;
}
bool cmp(const Query& a, const Query& b)
{
  int x = get(a.l), y = get(b.l);
  if (x == y) return (x & 1) ? a.r < b.r : a.r > b.r;
  return x < y;
}
void add(int x, int& res)
{
  // if (!cnt[x]) res ++;
  // else res += cnt[x] << 1 | 1;
  res += cnt[x] << 1 | 1;
  cnt[x] ++;
}
void del(int x, int& res)
{
  // if (cnt[x]) res += 1 - 2 * cnt[x];
  res -= 2 * cnt[x] - 1;
  cnt[x] --;
}
int main()
{
  n = rd(), m = rd(), k = rd();
  len = sqrt(n);
  for (int i = 1; i <= n; i ++ ) w[i] = rd();
  for (int i = 0; i < m; i ++ )
  {
    int l, r;
    l = rd(), r = rd();
    // cout << l << " " << r << endl;
    q[i] = {l, r, i};
  }
  sort(q, q + m, cmp);
  // for (int i = 0; i < m; i ++ )
  //   printf("q[i].l = %d, q[i].r = %d\n", q[i].l, q[i].r);
  for (int i = 0, l = 1, r = 0, res = 0; i < m; i ++ )
  {
    while (l < q[i].l) del(w[l ++ ], res);
    while (l > q[i].l) add(w[-- l ], res);
    while (r < q[i].r) add(w[++ r ], res);
    while (r > q[i].r) del(w[r -- ], res);
    // printf("id = %d, L = %d, R = %d, res = %d\n", q[i].id, q[i].l, q[i].r, res);
    ans[q[i].id] = res;
  }
  for (int i = 0; i < m; i ++ ) printf("%d\n", ans[i]);
  return 0;
}

  

 

posted @ 2021-07-25 10:48  rookie161  阅读(117)  评论(0编辑  收藏  举报