这篇文章主要是用来复习的,最近学了一些新的东西,多少要记录一下,不然以后忘了,不过似乎树状数组和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份。
n=read(); ll b = sqrt(n); for (int i = 1; i <= n; i++) { A[i]=read(); pe[i] = i / b; }
在这里作为输入端完成分块的操作。
那么在这里接下来就是莫队的精华,对于每一个询问的排序,若是两个询问的左边界在同一个块里面,那么这里就会去比较他们的右指针大小,否则就去比较他们的左指针的大小,如图是他们的比较代码。
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;
}
}
//树状数组可能是更优的解法,感觉,莫队会被卡掉
但是,这道题是用莫队会被卡掉,所以在这里,这道题过不了,但是可以看看数据弱化版的题目。 [](https://www.luogu.com.cn/problem/SP3267) [](https://www.spoj.com/problems/DQUERY/) 这个题可以过了,这里有两个链接,是同一个题目 [](https://www.luogu.com.cn/problem/P1494) 然后就就是这道题,也是一个莫队的题目,但是他会相对复杂一些,这里其实也是一个统计种类数的问题,那么同样,是用莫队去做这个题,首先还是对于每一个询问,重新排序,但是除了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;
}`
那么就到这里了