首页 > 编程语言 >莫队算法

莫队算法

时间:2024-08-21 10:38:49浏览次数:13  
标签:cnt int sqrt ++ 算法 -- now 莫队

前言

莫队是由莫涛提出的一种离线算法,是分块与双指针的结合,一般可以在 \(O(n \sqrt n)\) 或者 \(O(n \sqrt m)\) 的复杂度解决一些种类问题。

普通莫队

SP3267 DQUERY - D-query

给你一个长度为 \(n\) 的数列 \(A\),\(m\) 次询问,每次询问区间 \([l,r]\) 内的不同数字的个数。

如果要在线的话,需要用主席树。

我们考虑离线,把每个操作离线下来,然后挂到序列上。

每一个颜色代表一个询问。

可以想到用双指针来做,代表区间 \([l, r]\),如果任意指针移动就更新答案,是 \(O(1)\) 的。

想象是美好的,现实是残酷的。

如果询问是这样子的话:

那么尽管排完序左端点的移动可以做到 \(O(n)\),但是啊但是,右端点是无序的,就又会把复杂度打会 \(O(nm)\)。

那么这时候,莫队诞生了。

莫队在原来的基础上加了一个分块与排序。

可以证明复杂度是 \(O(n\sqrt n)\) 的。

对于左端点,如果每一个块中有 \(x\) 个询问的左端点,那么对于这个块的最坏复杂度是 \(O(x_i\sqrt n)\)。对于整个块,跨越一次需要 \(O(\sqrt n)\),最坏会跨整个序列,也就是 \(O(n)\),那么复杂度就是 $ O (\sum x_i\sqrt n + n\sqrt n) = O (n \sqrt n)$。

对于右端点,因为已经排完序,最坏需要跨整个序列,也就是 \(O(n)\),有 \(O(\sqrt n)\) 块,所以复杂度就是 \(O(n \sqrt n)\)。

所以总复杂度就是 \(O(n \log n + n \sqrt n + n \sqrt n) = O(n \sqrt n)\)。

回到这题,我们已经可以基本写出来了。

int n, m, len, tot;
int a[N], cnt[V];

bool cmp (Query A, Query B) { return A.x / len == B.x / len ? (A.y == B.y ? 0 : ((A.x / len) & 1) ^ (A.y < B.y)) : A.x < B.x; }
bool Cmp (Query A, Query B) { return A.id < B.id; } // 这里用了两个 cmp,实际不需要,用一个 ans 数组记录即可。

// 移动指针的修改
void add (int x) {
	if (!cnt[a[x]]) tot++;
	cnt[a[x]]++;
}

void del (int x) {
	if (cnt[a[x]] == 1) tot--;
	cnt[a[x]]--;
}

int main () {
	
	len = sqrt (n); // 这句话千万别漏了
	sort (q + 1, q + m + 1, cmp);
	
	for (int i = 1, l = 1, r = 0; i <= m; ++i) {
		while (l > q[i].x) add (--l);
		while (r < q[i].y) add (++r);
		while (l < q[i].x) del (l++);
		while (r > q[i].y) del (r--);
		
		q[i].ans = tot;
	}
	
	sort (q + 1, q + m + 1, Cmp);
  
	return 0;
}

带修莫队

P1903 [国家集训队] 数颜色 / 维护队列

给你一个长度为 \(n\) 的序列 \(A\) 以及 \(m\) 个操作,要你支持单点修改,并且询问区间颜色种类数。

现在带修了(恼)。

我们可以把每个询问抽象成 \((l, r)\) 的一个点对,这样我们可以构建出一个二维空间。

我们不妨把每一个操作用时间戳来表示,这样我们就可以多一个维度 \(t\),用来记录每次询问的时间维度。

初始化时,只需要找到最近的那个时间维度即可。

再来考虑考虑这个维度该怎么转移。

我们假设当前这个区间已经修改了 \(x\) 次,要转移到修改 \(y\) 次的序列。

如果 \(x \lt y\),那么我们需要把 \(x + 1\) 到 \(y\) 个修改全都加上。

如果 \(x \gt y\),那么我们需要把 \(x\) 到 \(y + 1\) 个修改全部还原。

什么?你问 \(x = y\)?

这种情况还需要转移吗。。。

于是我们在原来莫队的基础上加了一维,成功地实现了带修莫队。

int n, m, len, qcnt, rcnt;
int a[N], ans[N], cnt[V], tot;

struct Query {
	int id, l, r, t;
	
	bool operator < (const Query &T) {
		if (l / len != T.l / len) return l < T.l;
		if (r / len != T.r / len) return r < T.r;
		return t < T.t;
	}
} q[N];

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

void del (int x) {
	if (cnt[x] == 1) tot--;
	cnt[x]--;
}

int main () {

	for (int i = 1; i <= m; ++i) {
		char opt;
		int x, y;
		cin >> opt >> x >> y;
		
		if (opt == 'Q') q[++qcnt] = (Query){qcnt, x, y, rcnt};
		else r[++rcnt] = (Modify){x, y};
	}
	
	len = pow (n, 2.0 / 3.0);
	
	int L = 1, R = 0, now = 0;
	sort (q + 1, q + qcnt + 1);
	for (int i = 1; i <= qcnt; ++i) {
		while (L > q[i].l) add (a[--L]);
		while (R < q[i].r) add (a[++R]);
		while (L < q[i].l) del (a[L++]);
		while (R > q[i].r) del (a[R--]);
		while (now < q[i].t) {
			now++;
			if (L <= r[now].x && r[now].x <= R) {
				add (r[now].y);
				del (a[r[now].x]);
			}
			swap (a[r[now].x], r[now].y);
		}
		while (now > q[i].t) {
			if (L <= r[now].x && r[now].x <= R) {
				add (r[now].y);
				del (a[r[now].x]);
			}
			swap (a[r[now].x], r[now].y);
			now--;
		}
		
		ans[q[i].id] = tot;
	}
	
	return 0;
}

树上莫队

众所周知,莫队是用于序列上的一种算法,如果要用到树上,我们最先想到的就是把树序列化。

树上莫队?

但是一般的 DFS 序这样肯定是不行的,毕竟要方便统计路径上权值的种类数。

但是欧拉序很好的满足了这个要求。

如图,可以发现欧拉序只有标红圈的节点没有算上,而这个标红圈的节点正是 \(2\) 与 \(6\) 的 Lca。

所以我们只需要在转移过程中特判一下即可。

SP10707 COT2 - Count on a tree II

给你一棵 \(n\) 个节点的树,每次询问路径 \(u\) 到 \(v\) 上的权值种类数。

典,直接上。

const int N = 2e5 + 5;
int n, m, len; 

int dep[N], fa[N][31], lg[N], eu[N], ucnt;
int fi[N], ls[N];

void dfs (int now, int fath){
    fa[now][0] = fath;
    dep[now] = dep[fath] + 1;
    eu[++ucnt] = now;
    fi[now] = ucnt;

    for (int i = 1; i < 31; ++i){
        fa[now][i] = fa[fa[now][i-1]][i-1];
    }
    for (int i = head[now]; i; i = e[i].nxt)
        if (e[i].v != fath)
            dfs(e[i].v, now);
    
    eu[++ucnt] = now;
    ls[now] = ucnt;
}

int Lca (int x, int y)

void modify (int x) {
	if (!vis[x]) {
		if (!cnt[a[x]]) tot++;
		cnt[a[x]]++;
	} else {
		if (cnt[a[x]] == 1) tot--;
		cnt[a[x]]--;
	}
	
	vis[x] ^= 1;
}

int main () {
	
	ios::sync_with_stdio (false);
	cin.tie (0); cout.tie (0);
	
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		b[i] = a[i];
	}
	for (int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		addEdge (u, v);
		addEdge (v, u);
	}
	
	sort (b + 1, b + n + 1);
	int ret = unique (b + 1, b + n + 1) - b - 1;
	for (int i = 1; i <= n; ++i) a[i] = lower_bound (b + 1, b + n + 1, a[i]) - b;
	
	len = sqrt (n);
	
	for (int i = 1; i <= m; ++i) {
		int x, y;
		cin >> x >> y;
		
		if (fi[x] > fi[y]) swap (x, y);
		q[i].id = i, q[i].lca = Lca (x, y);
		
		if (q[i].lca == x) {
			q[i].x = fi[x];
			q[i].y = fi[y];
			q[i].lca = 0;
		} else {
			q[i].x = ls[x];
			q[i].y = fi[y];
		}
	}
	
	sort (q + 1, q + m + 1);
	
	int l = 1, r = 0;
	for (int i = 1; i <= m; ++i) {
		while (l > q[i].x) modify (eu[--l]);
		while (r < q[i].y) modify (eu[++r]);
		while (l < q[i].x) modify (eu[l++]);
		while (r > q[i].y) modify (eu[r--]);
		if (q[i].lca) modify (q[i].lca); // 注意特判
		ans[q[i].id] = tot;
		if (q[i].lca) modify (q[i].lca);
	}
	return 0;
}

标签:cnt,int,sqrt,++,算法,--,now,莫队
From: https://www.cnblogs.com/Xeanin/p/18371111

相关文章

  • 淘客返利机器人的智能化实现:架构与算法
    淘客返利机器人的智能化实现:架构与算法大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!在电商领域,淘客返利机器人作为一种高效的营销工具,其智能化实现对于提升用户体验和增加用户粘性具有重要意义。本文将深入探讨淘客返利机器人的架构......
  • 基础数据结构——二分算法及时间复杂度
    这个算法的前提是,数组是升序排列的算法描述:i和j是指针可以表示查找范围m为中间值当目标值targat比m大时,设置查找范围在m右边:i=m-1当目标值targat比m小时,设置查找范围在m左边:j=m+1当targat的值等于m时,返回m的下标如果最终没找到就返回-1;算法实现:publicintbir......
  • SFR算法原理分析
    成像系统的解析力:摄像头最关键的指标之一。所有用户拿到一张照片的时候首选看到的是照片清楚不清楚,这里的清楚指的就是解析力。但是如果评价一个成像系统的解析力也是大家一直在探讨的问题。目前主流的办法主要有三种TVline检测、MTF检测以及FR检测。MTF:MTF是ModulationTransfer......
  • 排序算法 常见排序算法特性比较
    目录排序的概念内外部排序稳定与非稳定排序改进排序的指标图片排序的概念排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原......
  • 排序算法 排序性能测试代码(随机数调整,高精度时间) - C++
    目录测试工具源码testsort测试工具C++11标准库<chrono>中高精度计时器,时间精度可以达到1纳秒.C++11标准库<random>中随机数生成器,可以实现各类随机数,本测试主要用于实现9成随机数下排序性能源码源码我拆分成两部分,一部分为测试,一部分为sort源码.合并一起使用test......
  • 密码学之哈希算法
    文章目录1.哈希函数概述1.1哈希函数的定义1.2哈希函数的重要性2.SHA系列算法简介2.1SHA系列的发展历史2.2SHA系列的应用场景3.主要SHA算法详解3.1MD5算法3.2SHA-1算法3.3SHA-2算法家族3.4SHA-3算法4.SHA算法的安全性分析4.1安全性的重要性4.2已知的攻击......
  • 密码学之RSA算法
    文章目录1.RSA算法介绍1.2算法历史与发展1.3算法应用场景2.RSA密钥生成2.1选择素数2.2计算公钥和私钥2.3密钥长度与安全性3算法原理3.1加密原理3.2加密方法3.3加密示例3.4代码实现4.总结1.RSA算法介绍1.2算法历史与发展RSA算法由RonRivest、Adi......
  • Manacher 算法
    引入:万恶的字符串问题你是否看到字符串就感到迷茫而无所适从?你是否看到“border”“回文”等字眼就感到大脑宕机只会暴力?如果你像我一样有这种症状,不妨一起来学习一下Manacher算法。当你掌握了Manacher之后,你会发现,很多字符串问题都变成了板子,而你再也不用担心因为字符串抱......
  • codetop算法
    15.三数之和复杂度显然不能暴力,卡1e9思路先排序,左边固定一个i,双指针,两边开始夹逼接近-nums[i]注意,如何跳过重复的数3.无重复字符的最长子串这个思路一定要熟练215.数组中的第K个最大元素回忆下写法53.最大子数组和基本不太会写dp究竟有什么特征?答案(定义)就......
  • 一文讲清楚算法刷题-计算机专业新生必看
    哈喽,大家好,我是Sunny,你也可以叫我萨宁,一个热爱分享编程知识的程序员。我的昵称是Sunny不要停,寓意是美好的晴朗日子不要停下来,希望大家都能每天开开心心的。我的频道主要分享编程知识,生活,大学计算机学科学习,考研经验。目前已经上岸某211计算机专业,有大学学习,考研相关的问题,欢迎关......