莫队用来离线解决区间询问问题。
一、不带修莫队
考虑利用分块的方法对所有询问的区间按照一定的顺序来回答,而不是完全按照输入顺序在线回答,从而使历史信息得到充分利用。这就是莫队相较于暴力要优的地方。
对$[1,n]$进行分块,每块大小为$B$。按照以下规则来给询问排序:对于两个区间,第一关键字是左端点所在的块。第二关键字是右端点所在的块。第三关键字是右端点的编号。其实在这里这第二关键字是没用的。然而之所以弄得复杂了点是为了更自然地衔接到下文要介绍的“带修莫队”中去。
当上一个回答的区间是$[l_1,r_1]$,此时要回答的区间是$[l_2,r_2]$时,暴力地移动左右端点,在移动的过程中进行更新或是统计。
下面来证明时间复杂度:
左端点依次分布在块中,但块内的左端点不单调。对于块内,每一次最多移动$B$个单位。而移动一次$O(1)$。总共移动$(m)$次。故复杂度为$O(mB)$
右端点相对于每一个块是单调的。因此处理一个块右端点最多移动$n$个单位。总共$\dfrac{n}{B}$个块。因此复杂度为$O(\dfrac{n^2}{B})$
因此复杂度为$O(mB+\dfrac{n^2}{B})$
复杂度相关
利用对勾函数(均值不等式)可以知道这个函数的最小值为$O(n\sqrt{m})$,$size$应当取$\sqrt{\dfrac{n^2}{m}}$。当$n=m$时,$B$取$\sqrt{n}$
模板(洛谷P2709 小B的询问)
#include#include #include #include #define ll long long using namespace std; inline int read(){ int x(0),w(1); char c = getchar(); while(c^'-' && (c<'0'||c>'9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c>='0' && c<='9') x = (x<<3) + (x<<1) + c - '0', c = getchar(); return x*w; } struct Query{ int l,r,idx; ll ans; }q[50010]; int n,m,K,L,R,sz; ll ans; int a[50010],cnt[50010]; inline bool cmp(const Query& A, const Query& B){ if(A.l/sz != B.l/sz) return A.l/sz < B.l/sz; return A.r < B.r; } inline bool cmp2(const Query& A, const Query& B){ return A.idx < B.idx; } inline void inc(int P){ ans += (ll)2*(ll)cnt[a[P]]+(ll)1; ++cnt[a[P]]; } inline void dec(int P){ ans -= (ll)2*(ll)cnt[a[P]]-(ll)1; --cnt[a[P]]; } int main(){ n = read(), m = read(), K = read(); sz = 223; for(int i = 1; i <= n; ++i) a[i] = read(); for(int i = 1; i <= m; ++i){ q[i].idx = i; q[i].l = read(), q[i].r = read(); } sort(q+1,q+m+1,cmp); L = R = 1; inc(1); for(int i = 1; i <= m; ++i){ while(R < q[i].r) inc(++R); while(R > q[i].r) dec(R--); while(L < q[i].l) dec(L++); while(L > q[i].l) inc(--L); q[i].ans = ans; } sort(q+1,q+m+1,cmp2); for(int i = 1; i <= m; ++i){ printf("%lld\n",q[i].ans); } return 0; }
二、带修莫队
我们对于每一个区间需要三维:左端点,右端点,时间。
按照以下规则来给询问排序:对于两个区间,第一关键字是左端点所在的块。第二关键字是右端点所在的块。第三关键字是时间。我们发现前两个关键字和不带修莫队的排序方法是一样的。
关键在于复杂度和块的最优大小证明。
我们考虑:左端点的移动次数是不变的,是$O(mB)$;右端点现在多出了块内的移动,一样是$O(mB)$,块间移动近似$O(\dfrac{n^2}{B})$,因此复杂度的和近似为$O(mB+\dfrac{n^2}{B})$。时间指针的复杂度是$O(c(\dfrac{n}{B})^2)$。由此,总复杂度为$O(c(\dfrac{n}{B})^2+mB+\dfrac{n^2}{B})$。
这个东西分析起来太麻烦啦!设$c=n=m$,复杂度为$y=O(\dfrac{n^3}{B^2}+nB+\dfrac{n^2}{B})$。我们要求$y$的最小值
$\dfrac{y}{n}=\dfrac{n^2}{B^2}+B+\dfrac{n}{B}$
令$t=\dfrac{n}{B}$,则可设$\dfrac{y}{n}=t^2+\dfrac{n}{t}+t$
由于当$t\rightarrow \infty$时,$t^2$为$t$的高阶,因此$\dfrac{y}{n} \approx t^2+\dfrac{n}{t}$
$t^2+\dfrac{n}{t}=t^2+\dfrac{n}{2t}+\dfrac{n}{2t} \geq 3\sqrt[3]{t^2*\dfrac{n}{2t}*\dfrac{n}{2t}}=3\sqrt[3]{\dfrac{n^2}{4}}$
因此$\dfrac{y}{n} \geq O(n^{\frac{2}{3}})$
因此$y_{min}=O(n^{\frac{5}{3}})$
而只有当$t^2=\dfrac{n}{2t}$时可以满足等号,因此$B$应当取$n^{\frac{2}{3}}$
模板(洛谷P1093 [国家集训队]数颜色 / 维护队列 )
在修改时有一个很妙的操作,那就是每一次修改交换修改的值和原值,因为下一次修改这个时一定是倒着修改了。
/*DennyQi 2019*/ #include#include #include using namespace std; inline int read(){ int x(0),w(1); char c = getchar(); while(c^'-' && (c<'0'||c>'9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c>='0' && c<='9') x = (x<<3) + (x<<1) + c - '0', c = getchar(); return x*w; } struct Query{ int l,r,t,idx,ans; }q[50010]; int n,m,T,x,y,Q,ans,L,R,cur_T,sz; int a[50010],b[50010],md[50010],mdc[50010],cnt[1000010]; char s[10]; inline bool cmp(const Query& A, const Query& B){ if(A.l/sz != B.l/sz) return A.l < B.l; if(A.r/sz != B.r/sz) return A.r < B.r; return A.t < B.t; } inline bool cmp2(const Query& A, const Query& B){ return A.idx < B.idx; } inline void inc(int P){ if(cnt[a[P]]++ == 0) ++ans; } inline void dec(int P){ if(--cnt[a[P]] == 0) --ans; } inline void modify(int i, int _L, int _R){ int P = md[i]; if(_L <= P && P <= _R){ if(--cnt[a[P]] == 0) --ans; swap(a[P],mdc[i]); if(cnt[a[P]]++ == 0) ++ans; } else swap(a[P],mdc[i]); } int main(){ scanf("%d%d",&n,&m); sz = 1357; for(int i = 1; i <= n; ++i) scanf("%d",a+i); for(int i = 1; i <= m; ++i){ scanf("%s",s); if(s[0] == 'Q'){ ++Q; scanf("%d%d",&q[Q].l,&q[Q].r); q[Q].idx = Q, q[Q].t = T; } else{ ++T; scanf("%d%d",&md[T],&mdc[T]); } } sort(q+1,q+Q+1,cmp); inc(L=R=1); for(int i = 1; i <= Q; ++i){ while(L < q[i].l) dec(L++); while(L > q[i].l) inc(--L); while(R < q[i].r) inc(++R); while(R > q[i].r) dec(R--); while(cur_T < q[i].t) modify(++cur_T,q[i].l,q[i].r); while(cur_T > q[i].t) modify(cur_T--,q[i].l,q[i].r); q[i].ans = ans; } sort(q+1,q+Q+1,cmp2); for(int i = 1; i <= Q; ++i){ printf("%d\n",q[i].ans); } return 0; }
三、树上莫队
太难了,咕咕咕