算法总结篇---莫队

算法简介

发明者:队爷莫涛

一种“优雅的暴力”

因善于无限卡常而闻名OI界

主要思想也十分简单:

暴力,也用到了排序,分块。

实际的操作方式就是通过改变枚举区间的顺序来达到优化复杂度的目的

虽然听上去很玄学,但却十分有效

复杂度在 (O(n^{frac{3}{2}})) 左右

一般是将查找区间分成 (n^{frac{1}{2}})

更优的复杂度可以进行按块奇偶排序来实现

普通莫队只支持离线操作,在线操作还是十分鸡肋的

但难不倒我们OIer

后人又总结出回滚莫队带修莫队树上莫队等多种方法,还没学,先咕咕着

要注意枚举的顺序,详见(Oi-Wiki)关于四个循环问题的讨论,主要思路是将区间先扩大再缩小

例题

小B的询问

小B的询问

Solution

利用莫队思想对所有查询的区间进行分块排序,顺便加上奇偶优化

每增加一个数,它对答案的贡献等于 (c^{2} - (c - 1)^{2})

每减掉一个数,它对答案的贡献等于 ((c - 1)^{2} - c^{2})

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long

using namespace std;
const int MAXN = 5e4+5;
const int INF = 1;
const int mod = 1;
struct node{
	int l, r, bh;
}b[MAXN];

int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return s * w;
}

int n, m, k, qrtn;
int a[MAXN];
LL Ans[MAXN], ans;
int cnt[MAXN];

bool cmp(node x, node y){ return x.l / qrtn == y.l / qrtn ? (x.l / qrtn % 2 == 0 ? x.r > y.r : x.r < y.r) : x.l < y.l; }

void add(LL pos){
	int x = ++cnt[a[pos]];
	ans -= (x - 1) * (x - 1);
	ans += x * x;
}

void del(LL pos){
	int x = --cnt[a[pos]];
	ans -= (x + 1) * (x + 1);
	ans += x * x;
}

int main()
{
	n = read(), m = read(), k = read();
	for(int i = 1; i <= n; ++i) a[i] = read();
	for(int i = 1; i <= m; ++i){
		b[i].l = read(), b[i].r = read();
		b[i].bh = i;
	}
	qrtn = sqrt(n + 0.5);
	sort(b + 1, b + m + 1, cmp);
	int l = 1, r = 0;
	for(int i = 1; i <= m; ++i){
		while(l > b[i].l) add(--l);
		while(r < b[i].r) add(++r);
		while(l < b[i].l) del(l++);
		while(r > b[i].r) del(r--);
		Ans[b[i].bh] = ans;
	}
	for(int i = 1; i <= m; ++i){
		printf("%lld
", Ans[i]);
	}
	return 0;
}

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

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

Solution

不难发现,所有情况总数等于 (len * (len - 1) / 2),其中 (len) 是区间长度

设函数 (c(x) = x * (x - 1) / 2)

则每加一个元素对其的贡献为 (c(x) - c(x - 1))

同理减一个元素的贡献 (c(x - 1) - c(x))

最后的答案等于分子和分母的比,分子为每个元素的贡献,分母为 (c(len)),注意通分和特判特殊情况

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long

using namespace std;
const int MAXN = 5e4+5;
const int INF = 1;
const int mod = 1;

struct node{
	int l, r, bh;
}b[MAXN];

inline int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return s * w;
}

int n, m, qrtn;
int a[MAXN];
int cnt[MAXN];
LL Ans[MAXN], ans;
LL Ansfm[MAXN];

inline bool cmp(node x, node y){ return x.l / qrtn == y.l / qrtn ? (x.l / qrtn % 2 == 0 ? x.r > y.r : x.r < y.r) : x.l < y.l; }
inline LL Gcd(LL x, LL y){ return y == 0 ? x : Gcd(y, x % y); }

inline LL c(LL x){	return x * (x - 1) / 2; }

inline void del(LL pos){
	int x = --cnt[a[pos]];
	ans -= c(x + 1);
	ans += c(x);
}

inline void add(LL pos){
	int x = ++cnt[a[pos]];
	ans -= c(x - 1);
	ans += c(x);
}

int main()
{
	n = read(), m = read();
	for(register int i = 1; i <= n; ++i) a[i] = read();
	for(register int i = 1; i <= m; ++i){
		b[i].l = read(), b[i].r = read();
		b[i].bh = i;
	}
	qrtn = sqrt(n + 0.5);
	sort(b + 1, b + m + 1, cmp);
	int l = 0, r = -1;
	for(register int i = 1; i <= m; ++i){
		while(l > b[i].l){ l--; add(l); }
		while(r < b[i].r){ r++; add(r);	}
		while(l < b[i].l){ del(l); l++;	}
		while(r > b[i].r){ del(r); r--;	}
		Ans[b[i].bh] = ans;//fenzi
		Ansfm[b[i].bh] = c(r - l + 1);//fenmu
	}
//	cout<<qrtn<<endl;
	for(int i = 1; i <= m; ++i){
		if(!Ans[i]) printf("0/1
");
		else{
			LL gcd = Gcd(Ans[i], Ansfm[i]);
			printf("%lld/%lld
", Ans[i] / gcd, Ansfm[i] / gcd);	
		}
	}
	return 0;
}

数列找不同

数列找不同

Solution

(ans) 来记录有几个数相同

如果增加一个元素 (x) 后,(cnt[x] == 2)(ans++)

如果删掉一个元素 (x) 后,(cnt[x] == 1)(ans--)

最后统计答案时,(ans == 1),为 (Yes),否则为 (No)

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1;
const int mod = 1;
struct node{
	int l, r, bh;
}b[MAXN];

int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return s * w;
}

int n, m, k, qrtn;
int a[MAXN];
LL Ans[MAXN], ans;
int cnt[MAXN];

bool cmp(node x, node y){ return x.l / qrtn == y.l / qrtn ? (x.l / qrtn % 2 == 0 ? x.r > y.r : x.r < y.r) : x.l < y.l; }

void add(LL pos){
	int x = ++cnt[a[pos]];
	if(x == 2) ans++;
}

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

int main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; ++i) a[i] = read();
	for(int i = 1; i <= m; ++i){
		b[i].l = read(), b[i].r = read();
		b[i].bh = i;
	}
	qrtn = sqrt(n + 0.5);
	sort(b + 1, b + m + 1, cmp);
	int l = 1, r = 0;
	for(int i = 1; i <= m; ++i){
		while(l > b[i].l) add(--l);
		while(r < b[i].r) add(++r);
		while(l < b[i].l) del(l++);
		while(r > b[i].r) del(r--);
		Ans[b[i].bh] = (ans == 0) ? 1 : 0;
	}
	for(int i = 1; i <= m; ++i){ Ans[i] == 1 ? printf("Yes
") : printf("No
"); }
	return 0;
}

Little Elephant and Array

Little Elephant and Array

Solution

每加一个数,统计个数,看看有没有新的贡献。

注意每加一个数或减一个数都有可能做出贡献,如果多加了也要将相应的贡献减掉

Code

没加任何优化的莫队,当时第一次写,写的比较丑

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
const int MAXN = 1e5+5;
const int INF = 1;
const int mod = 1;

struct node{
	int l, r, bh, ans;
	bool operator < (const node &b) const {return l == b.l ? r < b.r : l < b.l; }
}b[MAXN];

int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return s * w;
}

int n, m, l = 1, r = 0, ans = 0;
int a[MAXN], cnt[MAXN];
bool vis[MAXN];

bool cmp(node x, node y){return x.l == y.l ? x.r < y.r : x.l < y.l; }
bool cmp2(node x, node y){return x.bh < y.bh; }

int main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; ++i) a[i] = read(), a[i] = a[i] > 1e5 ? 0 : a[i];
	for(int i = 1; i <= m; ++i) b[i].l = read(), b[i].r = read(), b[i].bh = i;
	sort(b + 1, b + m + 1, cmp);
	for(int i = 1; i <= m; ++i){
		while(l < b[i].l) {
			cnt[a[l]]--; l++;
			if(a[l - 1] == cnt[a[l - 1]] && !vis[a[l - 1]]) ans++, vis[a[l - 1]] = 1;
			if(a[l - 1] != cnt[a[l - 1]] && vis[a[l - 1]]) ans--, vis[a[l - 1]] = 0;
		}
		while(r < b[i].r) {
			cnt[a[++r]]++;
			if(a[r] == cnt[a[r]] && !vis[a[r]]) ans++, vis[a[r]] = 1;
			if(a[r] != cnt[a[r]] && vis[a[r]]) ans--, vis[a[r]] = 0;
		}
		while(r > b[i].r){
			cnt[a[r]]--; r--;
			if(a[r + 1] == cnt[a[r + 1]] && !vis[a[r + 1]]) ans++, vis[a[r + 1]] = 1;
			if(a[r + 1] != cnt[a[r + 1]] && vis[a[r + 1]]) ans--, vis[a[r + 1]] = 0;
		}
		b[i].ans = ans;
	}
	sort(b + 1, b + m + 1, cmp2);
	for(int i = 1; i <= m; ++i){
		printf("%d
", b[i].ans);
	}
	return 0;
}

XOR and Favorite Number

CF617E XOR and Favorite Number

Solution

参考这位大佬的博客

Code

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define int long long 

using namespace std;
const int MAXN = 1e5+5;
const int kMax = 1e6+6;
const int INF = 1;
const int mod = 1;
struct node{
	int l, r, bh;
}b[MAXN];

int n, m, k, qrtn;
int a[MAXN];
LL Ans[MAXN], ans;
int cnt[kMax << 1];

int read(){
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
	return s * w;
}

bool cmp(node x, node y) { return x.l / qrtn == y.l / qrtn ? x.r < y.r : x.l < y.l; }

void add(int pos){
	ans += (LL)cnt[a[pos] ^ k];
	++cnt[a[pos]];
}

void del(int pos){
	--cnt[a[pos]];
	ans -= (LL)cnt[a[pos] ^ k];
}

signed main()
{
	n = read(), m = read(), k = read();
	for(int i = 1; i <= n; ++i) a[i] = read(), a[i] ^= a[i - 1];
	for(int i = 1; i <= m; ++i){
		b[i].l = read(), b[i].r = read(), b[i].bh = i;
	}
	qrtn = sqrt(n + 0.5);
	sort(b + 1, b + m + 1, cmp);
	int l = 0, r = -1;
	cnt[0] = 1;
	for(int i = 1; i <= m; ++i){
		while(l > b[i].l) l--, add(l - 1);
		while(r < b[i].r) r++, add(r);
		while(l < b[i].l) del(l - 1), l++;
		while(r > b[i].r) del(r), r--;
		Ans[b[i].bh] = ans;
	}
	for(int i = 1; i <= m; ++i){
		printf("%lld
", Ans[i]);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Silymtics/p/14130615.html