化暴力为标算——莫队
来源:互联网 发布:混也是一种生活 知乎 编辑:程序博客网 时间:2024/06/05 22:39
莫队。。这个听起来非常高端的东西,当时听大佬们说这个名词就一脸害怕,学完发现其实也还好。
之所以叫这个名字 因为发明者的缘故而已。。和算法本身并没有什么直接关系。
莫队就是用区间来优化暴力,可以把暴力的复杂度压到n^1.5左右
当然,常数有多大我就不知道了
莫队算法的本质就是暴力枚举。
对于m个询问,就是把m用一种方法排序后,直接暴力
考虑一下,,如果说我们知道 l~r之间的答案,
那么可以用O(1)的时间来得到l-1~r,l~r+1的答案
所以对询问排序啊!
好。。我知道会被一些极端数据卡死O(n^2)
但是反之,对于另一种极端情况是不是可以很快?O(n)
莫队的作用就是让复杂度均摊,于是就出现了O(n^1.5)
排序方法就是:分块
例如我们将n个数分成长度为n^0.5的n^0.5(+1)块
然后按每个询问的l所在区间排序
是不是就可以了?
每次跳一个区间的复杂度最多不过是n^0.5而已
我用lin[i]表示第i个数所在区间,排序的判断如下:
inline bool cmp(node x,node y){ if(lin[x.l]==lin[y.l]) return x.r<y.r; return lin[x.l]<lin[y.l];}
接下来,就是暴力了。。
是不是感觉很简单=v=+
下面以bzoj3289 mato的文件管理为例
传送门——bzoj3289
题意:给定一个序列a[1..n],询问l到r区间内逆序对数量
逆序对。。第一反应merge。。然而显然不可行
然后看了黄学长的博客,才想到树状数组求逆序对
树状数组:
inline int lowbit(int x){ return x&-x;}inline int get(int x){ int sum=0; for(int i=x;i;i-=lowbit(i)) sum+=f[i]; return sum;}inline void add(int x,int v){ for(int i=x;i<=n;i+=lowbit(i)) f[i]+=v;}
我们可以记录rank[i]表示a[i]在升序序列中的位置
那就方便了啊,当要用l~r求l~r+1的时候,就可以add(rank[r+1],1),反之,求l-1~r时,就可以add(rank[l-1],-1);
于是就变成了一道莫队的水题啊。。在n^1.5复杂度内解决问题
inline void doit(){ int ans=0; sort(q+1,q+m+1,cmp); for(int i=1,l=1,r=0;i<=m;i++) { for(;l<q[i].l;l++) {add(rank[l],-1);ans-=get(rank[l]-1); } for(;r>q[i].r;r--) {add(rank[r],-1);ans=ans-(r-l-get(rank[r])); } while(l>q[i].l) {l--;add(rank[l],1);ans+=get(rank[l]-1); } while(r<q[i].r) {r++;add(rank[r],1);ans+=(r-l+1-get(rank[r])); } q[i].ans=ans; }}
接下来输出:
inline bool cmp2(node x,node y){ return x.z<y.z;}
sort(q+1,q+m+1,cmp2); for(int i=1;i<=m;i++) printf("%d\n",q[i].ans);
CODE:
#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#include<algorithm>using namespace std;struct node{ int l,r,z,ans;}q[50001];int a[50001],t[50001],lin[50001],f[50001];int n,m;int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f;}inline bool cmp2(node x,node y){ return x.z<y.z;}inline bool cmp(node x,node y){ if(lin[x.l]==lin[y.l]) return x.r<y.r; return lin[x.l]<lin[y.l];}inline int lowbit(int x){ return x&-x;}inline int get(int x){ int sum=0; for(int i=x;i;i-=lowbit(i)) sum+=f[i]; return sum;}inline void add(int x,int v){ for(int i=x;i<=n;i+=lowbit(i)) f[i]+=v;}inline void doit(){ int ans=0; sort(q+1,q+m+1,cmp); for(int i=1,l=1,r=0;i<=m;i++) { for(;l<q[i].l;l++) {add(a[l],-1);ans-=get(a[l]-1); } for(;r>q[i].r;r--) {add(a[r],-1);ans=ans-(r-l-get(a[r])); } while(l>q[i].l) {l--;add(a[l],1);ans+=get(a[l]-1); } while(r<q[i].r) {r++;add(a[r],1);ans+=(r-l+1-get(a[r])); } q[i].ans=ans; }}int main(){ n=read(); for(int i=1;i<=n;i++) t[i]=a[i]=read(); sort(t+1,t+n+1); for(int i=1;i<=n;i++) a[i]=lower_bound(t+1,t+n+1,a[i])-t; m=read(); for(int i=1;i<=m;i++) { q[i].l=read();q[i].r=read(); q[i].z=i; } int block=sqrt(n); //tot=tot+(block%n!=0); for(int i=1;i<=n;i++) lin[i]=(i-1)/block+1; doit(); sort(q+1,q+m+1,cmp2); for(int i=1;i<=m;i++) printf("%d\n",q[i].ans);}
复杂度证明:
显然,l向前移动不会超过一个区间的长度。所以l最多移动次数:n^1.5,此时每次向前移动都移动至区间最右端,并且每次移到最左。
r同理
所以l和r移动次数大约在n*sqrt(n)的范围内,接下来就是乘上每次修改的复杂度了,以上题为例,复杂度在极端情况下为n*sqrt(n)*log(n)
这就是莫队算法了。。
谢谢看到这里
- 化暴力为标算——莫队
- ACM学习感悟——暴力专场E 暴力dp
- 暴力——HDU 4858
- 【NOIP】Sequence——暴力
- 暴力——HDU 4569
- DP转暴力。神奇的暴力+优化,神奇了我的大暴力——第一弹
- 色情暴力——媒体的毒瘤
- hdu——4462(暴力枚举)
- POJ2381——TOYS(暴力,叉积)
- Spiral——找规律暴力
- 算法复习1——暴力法
- POJ2718——Smallest Difference(暴力瞎搞)
- POJ3187——Backward Digit Sums(暴力)
- 自创算法——暴力自动机
- 暴力(腊鸡)—— ACdream 1068
- 暴力——BZOJ2783/Luogu3252 [JLOI2012]树
- 暴力+map——Codeforces831C Jury Marks
- UVA 140——Bandwidth(暴力)
- 理解@Autowired,@Service,@Resource注解
- Java 之泛型通配符
- AnnotationConfigApplictionContext源码分析
- 小菜鸟学习三层
- 2.初始化工作
- 化暴力为标算——莫队
- js获取网页的鼠标坐标
- JS:复习一些常用的Array方法
- hibernate框架检索策略之get与load
- 插入排序
- [编程学习]-----有用的link汇总
- 配置https证书
- ReentrantLock
- 数据的表示总结