世界,赋予了我们生|

fufufuf

园龄:1个月粉丝:0关注:1

高级数据结构与算法---莫队

这篇文章主要是用来复习的,最近学了一些新的东西,多少要记录一下,不然以后忘了,不过似乎树状数组和ST表还没有补完,等后面有时间(不能拖拉)再去将他们给写完,然后就开始去学习一下计算几何,树形DP以及图论,啊啊啊啊啊啊,还要准备数学建模,哎,为什么明明都放假了,还要给自己找这么多事情呢,躺着好好玩玩原神不香吗,不行,以后早上起床要多喊几声“我已经放假了”,“我已经放假了”,“我已经放假了”来告诉自己现在已经放假了,不然绷得太紧会绷不住的。

还有一件事,先立上flag,要好好找个时间做一下自己的网站,后面的博客会迁移到上面,除了这个之外,还要做一个AI的项目,以及读一读西瓜书,好像给自己的事情安排的太多了啊,算了,先开始讲东西吧,废话有一说一,自己都觉得太多了

首先是莫队,莫队是大神莫涛提出的全新的数据结构

这一位是真的神犇,前国家队队长,%%%,由于只学了普通的莫队,所以在这里将会去只讲普通的莫队算法(因为只学了这么多)

那么现在就是去看一下这个题目

这个题就是一个普通莫队的板子题(这个题也可以是用线段树,以及树状数组来解决,但是感觉莫队算是一个优雅的解法)所以就讲一下莫队``

首先,莫队是一种离线的做法,而不是一种在线的做法, 离线的含义即为他需要将所有的结果处理完之后,然后再来输出,而在线则是说去做出一个答案之后就马上输出,那么莫队又是什么呢,下面这句话非常关键。

如果在查询完区间[l , r] 之后,就去可以在O(1)的时间里面去查询[l+1,r], [l-1,r], [ l,r+1] ,[l, r-1] 区间里面的答案,那么他的速度就会快上很多倍。但是,这一看不就是双指针嘛,那又有什么用处呢,除了这个之外,还有分治,这样子处理起来效率就会快上不少了。

那么看一下怎么实现的,直接上AC的代码了,这样子比较好理解

首先,我们要写上一个结构体,去存储上每一个询问。这里我们会记录他的左区间和他的右区间,在这里,如下所示

struct mo {
	ll l, r, id,ans;
};

然后id会记录他是第几个输入的数据,这个用处很大,后面会讲解到,然后ans是用来记录答案的。

在继续下一步之前,先来看一下莫队的时间复杂度证明,莫队算法会将整个序列分为√n块,首先对于左指针,需要移动√n次,而对于右指针,则是要移动n次,所以在这里,由于存在√n个快,所以在这里,总共的移动次数应该是不超过O(n√n+n)的,所以这个便是莫队算法的时间复杂度,为O(n√n)。

那么为什么要分为√n块呢,在这里,假设块长为s,那么现在就是有n/s个块,然后由于每个块存在m个询问,第i个块询问的数量为q,那么,最坏的时间复杂度应该是
 如下所示:

那么如果近似m和n,就可以知道最后的大小应该是√n,在这里就可以完成莫队算法的复杂度证明以及为什么要分成√n份。

struct mo {
	ll l, r, id,ans;
};

在这里作为输入端完成分块的操作。

那么在这里接下来就是莫队的精华,对于每一个询问的排序,若是两个询问的左边界在同一个块里面,那么这里就会去比较他们的右指针大小,否则就去比较他们的左指针的大小,如图是他们的比较代码。

inline bool cmp(mo a, mo b) {
	if (pe[a.l] == pe[b.l]) {
		return a.r < b.r;
	}
	return a.l < b.l;
}

在这之后,就是关于指针的移动,主要是包括了remove 和 add 这两个算法,这里主要是去用来维护每个询问的ans,换言之,就是去求解。

inline void add(ll i) {
	//当前的数字不存在
	if (!cnt[A[i]]) {
		now++;
	}
	cnt[A[i]]++;
}

inline void rem(ll i) {
	cnt[A[i]]--;
	if (!cnt[A[i]]) {//当前的数字为0
		now--;
	}
}

会使用now变量来记录当前的答案。那么在这里,最后就是完整的代码了,

#include<bits/stdc++.h>
#define ll long long
const ll maxn = 1e6 + 7;
using namespace std;

struct mo {
	ll l, r, id,ans;
};

inline ll read() {
	ll x = 0;
	ll f = 1;
	char c = getchar();
	while (c < '0' || c>'9') {
		if (c == '-') {
			f = -1;
		}
		c = getchar();
	}

	while (c >= '0' && c <= '9') {
		x = x * 10 + c - '0';
		c = getchar();
	}
	return x * f;
}

ll pe[maxn];
ll A[maxn];//记录原始数组
ll cnt[maxn];//记录当前区间中每个数字的数量
mo mos[maxn];
ll n, m;
ll now = 0;

inline bool cmp(mo a, mo b) {
	if (pe[a.l] == pe[b.l]) {
		return a.r < b.r;
	}
	return a.l < b.l;
}

inline bool cmp2(mo a, mo b) {
	return a.id < b.id;
}

inline void add(ll i) {
	//当前的数字不存在
	if (!cnt[A[i]]) {
		now++;
	}
	cnt[A[i]]++;
}

inline void rem(ll i) {
	cnt[A[i]]--;
	if (!cnt[A[i]]) {//当前的数字为0
		now--;
	}
}

int main() {
	n=read();
	ll b = sqrt(n);
	for (int i = 1; i <= n; i++) {
		A[i]=read();
		pe[i] = i / b;
	}
	m=read();
	for (int i = 1; i <= m; i++) {
		mos[i].l = read();
		mos[i].r=read();
		mos[i].id = i;
	}

	sort(mos + 1, mos + m + 1, cmp);
	ll l = 1;
	ll r = 0;
	for (int i = 1; i <= m; i++) {
		while (l < mos[i].l) {
			rem(l++);
		}
		while (l > mos[i].l) {
			add(--l);
		}
		while (r < mos[i].r) {
			add(++r);
		}
		while (r > mos[i].r) {
			rem(r--);
		}
		mos[i].ans = now;
	}
	sort(mos + 1, mos + m + 1, cmp2);

	for (int i = 1; i <= m; i++) {
		cout<<mos[i].ans<<endl;
	}
}
//树状数组可能是更优的解法,感觉,莫队会被卡掉

但是,这道题是用莫队会被卡掉,所以在这里,这道题过不了,但是可以看看数据弱化版的题目。


这个题可以过了,这里有两个链接,是同一个题目

然后就就是这道题,也是一个莫队的题目,但是他会相对复杂一些,这里其实也是一个统计种类数的问题,那么同样,是用莫队去做这个题,首先还是对于每一个询问,重新排序,但是除了l 和r之外,在这里我们还要记录两个元素a,b用来最后求解答案,分别为当前袜子的总数,以及袜子的总种类数,那么,在这里,接下来就是去维护ans了,用来记录答案,对于每一个新加入的袜子,答案会增加他的同类袜子数量个,因为它可以与每一个同种类的袜子两两组合,所以说在这里就可以维护了,如下所示。

inline void add(ll i) {
	ans += mark[a[i]];
	mark[a[i]]++;
}

inline void del(ll i) {
	mark[a[i]]--;
	ans -= mark[a[i]];
}

对于分数化简,在这里,我们只要使用GCD即可。

最后代码如下所示。

#include<bits/stdc++.h>
#define ll long long
#define maxn (ll)5e4+9
using namespace std;

struct mo {
	ll l, r, id;
	ll a, b;
}pe[maxn];

ll ans = 0;
ll mark[maxn];
ll pos[maxn], a[maxn];

inline void add(ll i) {
	ans += mark[a[i]];
	mark[a[i]]++;
}

inline void del(ll i) {
	mark[a[i]]--;
	ans -= mark[a[i]];
}

inline ll gcd(ll a, ll b) {
	if (b != 0) {
		return gcd(b, a % b);
	}
	return a;
}

inline bool cmp(mo a, mo b) {
	return pos[a.l] == pos[b.l] ? a.r < b.r : a.l < b.l;
}

inline bool cmp2(mo a, mo b) {
	return a.id < b.id;
}

int main() {
	ll n, m;
	cin >> n >> m;
	ll b = sqrt(n);
	ans = 0;
	memset(mark, 0, sizeof(mark));

	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		pos[i] = i / b;
	}

	for (int i = 1; i <= m; i++) {
		cin >> pe[i].l>> pe[i].r;
		pe[i].id = i;
	}

	ll l = 1, r = 0;
	sort(pe + 1, pe + m + 1, cmp);
	for (int i = 1; i <= m; i++) {
		while (l < pe[i].l) {
			del(l++);
		}
		while (l > pe[i].l) {
			add(--l);
		}
		while (r > pe[i].r) {
			del(r--);
		}
		while (r < pe[i].r) {
			add(++r);
		}
		pe[i].a = ans;
		pe[i].b = (pe[i].r - pe[i].l + 1) * (pe[i].r - pe[i].l) / 2;

		if (ans == 0) {
			pe[i].b = 1;
			continue;
		}
		ll gcd1 = gcd(pe[i].a, pe[i].b);
		pe[i].a /= gcd1;
		pe[i].b /= gcd1;
	}
	sort(pe + 1, pe + m + 1, cmp2);
	for (int i = 1; i <= m; i++) {
		cout << pe[i].a << '/' << pe[i].b << endl;
	}
	return 0;
}

那么就到这里了

本文作者:芙芙芙啊

本文链接:https://www.cnblogs.com/fufufuf/p/18667025

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   fufufuf  阅读(18)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起