@XLM
2017-09-26T23:52:35.000000Z
字数 2703
阅读 428
莫队 算法思想 分块 离线处理
当处理题目中一个一个繁琐又冗杂的询问时,最好办法当然是在线处理,也就是问一个答一个。然而有时候在线处理会遇到出题人各种各样的陷阱,有些题也没办法使用线段树等适宜的在线算法。这时候就需要我们离线了。
离线干什么?当然,如果你离线后还打算暴搜,那你还是不要看这篇文章较好。
离线是为了什么,简单来说,就是为了一种更好的处理询问的顺序
如果你问:顺序到底有多重要?
我不说话,我们先看这样一道题:
我就没有这种丢袜子还能出出题的经历
简述一下题目大意,处理区间内随机取两个数相同的概率。
不难想出,对于区间l-r,答案为sum(C(2,f[i])/C(2,r-l+1)(其中f[i]为数值,C为组合数)
然后自然,也可以看到,在我们知道区间l-r的答案时,我们可以用O(1)求出区间(l-1)-r和区间l-(r+1)的值,具体方法
代码可以像这样:
void move(int po,ll &ans,int add){ans=ans+2*add*f[val[po]]+1;f[val[po]]+=add;}//解释一下代码,po为我当前要对l-r区间内加入或删除的位置,ans是维护的一个当前操作得到的一个值//add是一个标记,来标记是加入还是删除,1为加入,-1为删除//f数组呢,是我维护的当前区间的数的数量
我们当然可以暴力加入减少l和r,但是我们排个序,减少次数不是更好吗。
排序……朴素排序吗?
当然不行,因为如果分别用l和r作为关键字排序的话,很容易被卡到O(n^2)
比如这样,我给出一组数据
1 52 1003 54 100
如果按照l,r排序的话,会先加入到5,再到100,再减少为5再到100.
由于出题人常常是个恶趣味,他很可能会这么卡你卡到死
那么得按照一个别的排序方式,emmmmm
为了保证一个稳定的加入复杂度,我们想到了分块
就以左端点所在的块为第一关键字,右端点为第二关键字好了。
那么这样的复杂度呢,有证明认为在O(n^1.5)上下
粘上证明好了
右端点移动:首先我们考虑一个块里面的转移情况由于一个块里面的询问都按右端点排序所以我们右端点在一个块里面最多移动n次有 O(n√)O(n)个块,那么同一个块内的右端点移动最多就是O(nn√)O(nn)然后考虑从一个块到另一个块导致的右端点变化最坏情况,右端点由n到1,那么移动n次有 O(n√)O(n)个块那么从一个块到另一个块的事件只会发生O(n√)O(n)次……所以这种右端点移动的次数也是O(nn√)O(nn)次没有别的事件导致右端点移动了左端点移动:同一个块里面,由于左端点都在一个长度为O(n√)O(n)的区间里面所以在同一块里面移动一次,左端点最多变化O(n√)O(n)总共有n个询问……所以同一块里面的移动最多n次那么同一个块里面的左端点变化最多是O(nn√)O(nn)的考虑跨越块每由第i个块到第i+1个块,左端点最坏加上O(n√)O(n)总共能加上O(n√)O(n)次所以跨越块导致的左端点移动是O(n)O(n)的综上,分块做法是O(n∗n√)O(n∗n)。
我们给出AC代码好了
//来一发莫队#include<cstdio>#include<algorithm>#include<cstring>#include<iostream>#include<cmath>using namespace std;const int maxn=50010;typedef long long ll;ll gcd(ll a,ll b){return (b==0)?a:gcd(b,a%b);}int val[maxn],pos[maxn];struct node{int l,r,id;ll a,b;void sim(){ll temp=gcd(a,b);a/=temp,b/=temp;}bool operator <(const node &rhs)const{if (pos[l]==pos[rhs.l]) return pos[r]<pos[rhs.r];else return pos[l]<pos[rhs.l];}}Q[maxn];bool cmp_id(const node &a,const node &b){return a.id<b.id;}int f[maxn],n,m,limit;void read(){scanf("%d%d",&n,&m);for (int i=1;i<=n;i++){scanf("%d",&val[i]);}limit=(int)sqrt(double(n+0.5));for (int i=1;i<=n;i++)pos[i]=(i-1)/limit+1;for (int i=1;i<=m;i++){scanf("%d%d",&Q[i].l,&Q[i].r);Q[i].id=i;}sort(Q+1,Q+m+1);}void move(int po,ll &ans,int add){ans=ans+2*add*f[val[po]]+1;f[val[po]]+=add;}void deal(){ll ans=0;int l=1,r=0;for (int i=1;i<=m;i++){while (r<Q[i].r) r++,move(r,ans,1);while (Q[i].l<l) l--,move(l,ans,1);while (Q[i].r<r) move(r,ans,-1),r--;while (l<Q[i].l) move(l,ans,-1),l++;Q[i].a=ans-(Q[i].r-Q[i].l+1);Q[i].b=(ll)(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l);Q[i].sim();}sort(Q+1,Q+m+1,cmp_id);for (int i=1;i<=m;i++){printf("%lld/%lld\n",Q[i].a,Q[i].b);}}int main(){read();deal();}
那么,一篇文章,莫队来莫队去的,莫队是什么啊
emmm……莫队是种思想。
是什么
在考虑一个不满足区间加和性的的问题时,我们没办法使用我们一般使用惯了的数据结构(线段树什么的),这时候我们就想,这些询问中有没有某种规律可以让我们减少复杂度和操作次数。
如果有的话我们换个方式,或者说,换个顺序,处理这些询问以便更加有复杂度减少方法。
为什么
因为这样可以搞事情啊
怎么用
就是排序分块吧
什么时候呢?
首先,这样一个一个改变区间的操作的复杂度必须在常数级别。
第二,可以离线(有一些强制在线的题)
然后呢,再给出几道题(待补充)
HH的项链(不带修改)
数颜色(带修改)