莫队/se 优雅的暴力

莫队算法

发明者:队爷莫涛
基于分块的一种暴力算法, 复杂度最慢可以被卡到\(n^2\)正常情况下的复杂度大约在\(O(n\sqrt{n})\)左右分块的大小对复杂的影响很大其中最优分块的大小为\(\dfrac {s}{\sqrt{m}}\) 最优复杂度为\(O(n\sqrt{m})\)

证明

用处:维护区间信息

具体做法

  • 对求的\(l-r\)区间进行排序,根据\(l\)\(r\)所在块的位置,进行排序
  • 对排序后的\(l-r\)的区间进行维护,观察维护的数据具有什么特点

注意:

  • 4个\(while\)循环不能乱,根据先后关系进行确定(24中全排列,6中正确)
    莫队的枚举顺序与区间有很大的关系

    如果先枚举 \(l\to l', r\to r'\)就会发现\(r \to l'\)这一块会先减一遍然后再加一遍,但是会出现一个问题\(r\to l'\)第一遍减的时候不会减到负的只会变成 0 所以会挂掉,推荐一个枚举的顺序:

\(l--, r--, r++, l++\)对称的比较好记

  • 莫队比较卡常,注意写小常数
    例题
    这个就是概率问题
  • 例子1 eg:3 3 4 5 6 4
    \(ans = \dfrac{2}{6} \times \dfrac{1}{5} + \dfrac{2}{6} \times \dfrac{1}{5} = \dfrac{2}{15}\)
    莫队的过程就是
l = 1 ,r = 0 ,son = 0
l = 1 ,r = 1 ,son = 0 
l = 1 ,r = 2 ,son = 2 * ( 2 - 1 ) / 2
l = 1 ,r = 3 ,son = 2 * ( 2 - 1 ) / 2
l = 1 ,r = 4 ,son = 2 * ( 2 - 1 ) / 2
l = 1 ,r = 5 ,son = 2 * ( 2 - 1 ) / 2
l = 1 ,r = 6 ,son = 2 * ( 2 - 1 ) / 2 + 2 * (2 - 1) / 2
mo = 6 * (6 - 1) / 2
  • 例子1 eg: 3 3 3 4 5 6 4
l = 1 ,r = 0 ,son = 0
l = 1 ,r = 1 ,son = 2 * ( 2 - 1 ) / 2
l = 1 ,r = 2 ,son = 3 * ( 3 - 1 ) / 2
l = 1 ,r = 3 ,son = 3 * ( 3 - 1 ) / 2
l = 1 ,r = 4 ,son = 3 * ( 3 - 1 ) / 2
l = 1 ,r = 5 ,son = 3 * ( 3 - 1 ) / 2
l = 1 ,r = 6 ,son = 3 * ( 3 - 1 ) / 2 + 2 * (2 - 1) / 2
mo = 7 * (7 - 1) / 2

过程中先删去前一个的贡献再加上后一个的贡献

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#define orz puts("LKP AK IOI")
#define ll long long
using namespace std;
const int N = 5e4+100;
int read(){
	int s = 0 ,f = 1; char ch = getchar();
	while(ch < '0'||ch > '9'){if(ch == '-') f = -1 ; ch = getchar();}
	while(ch >= '0'&&ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
	return s * f;
}
ll sqrtn , l = -1, r = 0, ans;
ll son[N], mo[N];
struct node {
	int l, r, id;
	bool operator < (const node &x) const {
		if( l / sqrtn != x.l / sqrtn) return l < x.l;
		if((l/sqrtn) & 1) return r < x.r;
		return r > x.r;  
	}
}wa[N];
int cs[N],c[N];
ll ANS(ll  x) {
	return x < 2 ? 0: x * (x - 1) ;	
}
void del(int pos) {
	ll temp = --cs[c[pos]];
	ans -= ANS(temp+1);	ans += ANS(temp);
} 
void add(int pos) {
	ll temp = ++cs[c[pos]];
	ans -= ANS(temp-1);	ans += ANS(temp); 
}
ll gcd(ll a,ll b) {
	return b == 0 ? a : gcd(b, a%b); 
} 
bool cmp(node a,node b) {
	if(a.l/sqrtn == b.l/sqrtn)	return a.r < b.r;
	return a.l < b.l; 
}
int main(){
	int n = read() ,m = read()	;
	for(int i = 1 ; i <= n ; i++) 	c[i] = read();
	for(int i = 1 ; i <= m ;i++) 	wa[i].l = read() , wa[i].r = read() , wa[i].id = i; 
	sqrtn = sqrt(0.5+n );
	sort(wa+1,wa+1+m);
	//sort(wa+1 , wa+1+m, cmp); 
	//orz;
	for(int i = 1 ; i <= m ;i++) {
		while (l < wa[i].l) del(l),l++;
		while (l > wa[i].l) l--,add(l);
		while (r < wa[i].r) r++,add(r);
		while (r > wa[i].r) del(r),r--;
		son[wa[i].id] = ans;
		mo[wa[i].id] = ANS(r-l+1);
	}
	for(int i = 1; i <= m ;i++) {
		if(son[i] == 0) {
			cout<<"0/1\n";
			continue;
		}
		ll temp = gcd(son[i],mo[i]);
		printf("%lld/%lld\n",son[i]/temp,mo[i]/temp);
	}
	return 0;
}

upd:2021.3.13
今天突然发现自己记得莫队修改顺序不大对劲(好怪啊

突然感觉

  --L
  ++R
  L--
  R++

打着也挺顺手的

posted @ 2020-12-13 22:03  Imy_bisLy  阅读(104)  评论(0编辑  收藏  举报