一直要写莫队的总结都没时间写- -
今天下午把它写掉吧
莫队的思路就是对询问分块然后排序 使得总时间复杂度优化一个√(n)级别
具体还是拿一道题目来说吧- -
例题
【2009国家集训队】 小z的袜子
给出n个数表示n只袜子的颜色 询问l、r 表示区间[l,r] 内随机抽取两只袜子同色的概率
显然就是要求区间[l,r] 内有几对袜子是颜色相同的
暴力的做法 维护num[i]表示颜色为i的袜子有几只 时间O(nm)
莫队的做法就是 先把n只袜子分成√n块 每块√n
把所有的询问排序 按第一关键字为l所属的块 第二关键字为r
把第一关键字相同的一起处理
每块只有√n个点 所以相邻两次询问的l最多差√n 而r递增 所以这些询问的r变化总和为O(n)的
总时间复杂度O(n√n)
代码
1 #include <cmath> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 typedef long long ll; 6 using namespace std; 7 const ll N=50001; 8 struct info{ 9 ll x,y,t; 10 info(const ll a=0,const ll b=0,const ll c=0): 11 x(a),y(b),t(c){} 12 }ask[N],save[N]; 13 ll n,m,add,ns,col[N],num[N],ans[N],anss; 14 inline bool cmp(info a,info b){ return a.y<b.y; } 15 ll gcd(ll x,ll y){ return y ? gcd(y,x%y) : x; } 16 void addnum(ll x,ll y,ll s){ 17 for (ll i=x;i<=y;i++){ 18 if (num[col[i]]) anss-=num[col[i]]*(num[col[i]]-1); 19 num[col[i]]+=s; 20 anss+=num[col[i]]*(num[col[i]]-1); 21 } 22 } 23 void work(){ 24 ll add=(int)sqrt(n); 25 for (ll i=1;i<=n;i+=add){ 26 memset(num,0,sizeof(num)); 27 anss=0; 28 ns=0; 29 for (ll j=1;j<=m;j++) 30 if (i<=ask[j].x && ask[j].x<i+add) save[++ns]=ask[j]; 31 sort(save+1,save+ns+1,cmp); 32 for (ll last=i+add,j=1;j<=ns;j++){ 33 if (save[j].y<i+add) addnum(save[j].x,save[j].y,1); 34 else{ 35 addnum(save[j].x,i+add-1,1); 36 addnum(last,save[j].y,1); 37 last=save[j].y+1; 38 } 39 ans[save[j].t]=anss; 40 if (save[j].y<i+add) addnum(save[j].x,save[j].y,-1); 41 else addnum(save[j].x,i+add-1,-1); 42 } 43 } 44 } 45 int main(){ 46 freopen("bz2038.in","r",stdin); 47 freopen("bz2038.out","w",stdout); 48 scanf("%I64d%I64d",&n,&m); 49 for (ll i=1;i<=n;i++) scanf("%I64d",&col[i]); 50 for (ll x,y,i=1;i<=m;i++){ 51 scanf("%I64d%I64d",&x,&y); 52 ask[i]=info(x,y,i); 53 } 54 work(); 55 for (ll i=1;i<=m;i++){ 56 ll x=ans[i],y=ask[i].y-ask[i].x+1; 57 if (y==1){ 58 puts("0/1"); 59 continue; 60 } 61 y=y*(y-1); 62 ll gc=gcd(x,y); 63 printf("%I64d/%I64d ",x/gc,y/gc); 64 } 65 fclose(stdin); 66 fclose(stdout); 67 }
带询问
但是这题没有修改操作 如果有修改操作怎么办呢- -
我们可以把n平均分为3√n块
排序时第一关键字为l所在的块 第二关键字为r所在的块 第三关键字为询问/操作时间ti
对于前两个关键字相同的一起处理 处理这么一堆的时间复杂度为O(n) 所以总时间复杂度为O(n^(5/3))
支持在线
还有一种比较快的方法 而且支持在线(其实感觉都不叫莫队了 明明就是分块)
维护f[x][y]表示第x块到第y块的答案 每次修改O((3√n)^2) 询问则只要O(n/(3√n))
但是有个缺点 有时候需要维护的值不止一个 如上面每种袜子的个数 空间很可能会不够用