笔记——莫队

蓝月の笔记——莫队篇

简介

莫队算法是由莫涛提出的算法。在莫涛提出莫队算法之前,莫队算法已经在 Codeforces 的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。莫涛提出莫队算法时,只分析了普通莫队算法,但是经过 OIer 和 ACMer 的集体智慧改造,莫队有了多种扩展版本。

莫队算法可以解决一类离线区间询问问题,适用性极为广泛。同时将其加以扩展,便能轻松处理树上路径询问以及支持修改操作。但是我不会

这段话是抄的

摘要

暴力乱搞做法

Prob.

Luogu P1972 [SDOI2009] HH的项链

没有特别说明都是在讲这道题

Part 1 暴力搞法

很显然,可以使用 O(n2)在线回复询问,开一个 vis[] 将区间扫一遍即可

代码:

// BLuemoon_
#include <bits/stdc++.h>

using namespace std;

const int kMaxN = 1e6 + 5;

int n, m, l, r, ans, a[kMaxN], vis[kMaxN];

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (cin >> m; m; m--, ans = 0) {
    cin >> l >> r, fill(vis, vis + kMaxN, 0);
	for (int i = l; i <= r; i++) {
	  ans += (vis[i] == 0), vis[i]++;
	}
	cout << ans << '\n';
  }
  return 0;
}

Part 2 普通莫队

有聪明的小朋友就要问了,不是你就一个傻逼暴力为什么要把在线标粗啊

我的评价是:感觉不如莫队

用 rzx 也想的出来莫队是一个离线算法,这明显就是一个铺垫啊

我们可以先把所有询问离线下来,进行排序,然后按照一定顺序进行拓展,就可以在 O(nn) 的时间复杂度内算出结果了

那又有聪明的小朋友要问了,那排序的顺序是什么呢

首先我们明显可以在 O(1)[l,r] 的询问拓展到相邻的 [l,r+1],[l,r1],[l1,r],[l+1,r],那么我们可以以 l 为第一关键字,r 为第二关键字惊醒排序,这样就可以做到最优 O(n) 最坏 O(n2)

为什么会被卡到 O(n2) 呢,我们可以把询问 [l,r] 抽象成平面上的点 (l,r),两个询问之间的拓展次数就是他们的曼哈顿距离,有因为询问保证 lr,所以这些点都在 y=x 之上,于是我们就可以用这样的图形来卡这种做法

用 rzx 也看的出来这么算是 O(n2) 的,那么我们怎么优化呢,当然使用卡常界最泛用的分块啦

我们可以按 x 坐标分成 B 块,按点所在块的编号最为第一关键字,y 坐标作为第二关键字进行排序

这当块长为 n 时,可以将时间复杂度降到 O(nn) ,懒得证明了,自己看去吧

但是我们还可以进行常数优化,因为最坏情况下从前一个块跳到后一个块可能需要 O(n) 次拓展,所以我们可以对于奇数编号按 y 坐标从小到大排序,偶数编号从大到小排序,这样就可以稍微平缓的坐过山车了

代码:

// BLuemoon_
#include <bits/stdc++.h>

using namespace std;
using LL = long long;

const int kMaxN = 1e6 + 5;

int n, m, s, L = 1, R;
LL ans, a[kMaxN], c[kMaxN];

struct Q {
  int l, r, id;
  LL ans;
  const bool operator<(const Q& _) {
    int lb = (l - 1) / s + 1, rb = (_.l / s + 1);
    if (lb != rb) {
      return lb < rb;
    }
    return (lb & 1 ? r < _.r : r > _.r);
  }
  const bool operator>(const Q& _) {
    return id < _.id;
  }
};

Q q[kMaxN];

void Insert(int x) {
  c[a[x]]++, ans += (c[a[x]] == 1);
}
void Delete(int x) {
  c[a[x]]--, ans -= (c[a[x]] == 0);
}
bool CMP(Q l, Q r) {
  return l.id < r.id;
}

int main() {
  cin >> n, s = sqrt(n);
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  cin >> m;
  for (int i = 1; i <= m; i++) {
    cin >> q[i].l >> q[i].r, q[i].id = i;
  }
  sort(q + 1, q + m + 1);
  for (int i = 1; i <= m; i++) {
    for (; R < q[i].r; Insert(++R)) {
    }
    for (; L > q[i].l; Insert(--L)) {
    }
    for (; R > q[i].r; Delete(R--)) {
    }
    for (; L < q[i].l; Delete(L++)) {
    }
    q[i].ans = ans;
  }
  sort(q + 1, q + m + 1, CMP);
  for (int i = 1; i <= m; i++) {
    cout << q[i].ans << '\n';
  }
  return 0;
}

你说的对,但是这份代码只有 36 分,因为 O(nn) 可能达到 109,能过才怪,但是如果你卡卡常或许可以达到 5070 分,我在最多卡到了 52,加油吧

To be continue

posted @   BluemoonQwQ  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示