莫队算法的模板实现

今天的模拟赛T3是一道裸莫队..本来想了想之前被裸点分治虐的体无完肤决定打暴力的orz(果然我太弱啦!

然后听说莫队挺简单的,看看时间...嗯还有不少 那就学学莫队吧

莫队的思想理解起来还是不难的

比如这篇博客就写的很好http://blog.csdn.net/bossup/article/details/39236275

对于L,R的询问。设其中颜色为x,y,z....的袜子的个数为a,b,c。。。

那么答案即为(a*(a-1)/2+b*(b-1)/2+c*(c-1)/2....)/((R-L+1)*(R-L)/2)

化简得:(a^2+b^2+c^2+...x^2-(a+b+c+d+.....))/((R-L+1)*(R-L))

即:(a^2+b^2+c^2+...x^2-(R-L+1))/((R-L+1)*(R-L))

所以这道题目的关键是求一个区间内每种颜色数目的平方和。

但问题时怎么快速求解呢?

对于一般区间维护类问题一般想到用线段树。但是这题完全不知道线段树怎么做,百度了下。知道是莫队算法。

于是乎学习了下。写写学习的心得吧。

莫队算法是莫涛发明了。感觉这人蛮牛逼的。但是网上各种百度他的论文却找不到了。只好到别人的博客里学习学习。莫队算法是离线处理一类区间不修改查询类问题的算法。就是如果你知道了[L,R]的答案。你可以在O(1)的时间下得到[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案的话。就可以使用莫队算法。

对于莫队算法我感觉就是暴力。只是预先知道了所有的询问。可以合理的组织计算每个询问的顺序以此来降低复杂度。要知道我们算完[L,R]的答案后现在要算[L',R']的答案。由于可以在O(1)的时间下得到[L,R-1]和[L,R+1]和[L-1,R]和[L+1,R]的答案.所以计算[L',R']的答案花的时间为|L-L'|+|R-R'|。如果把询问[L,R]看做平面上的点a(L,R).询问[L',R']看做点b(L',R')的话。那么时间开销就为两点的曼哈顿距离。所以对于每个询问看做一个点。我们要按一定顺序计算每个值。那开销就为曼哈顿距离的和。要计算到每个点。那么路径至少是一棵树。所以问题就变成了求二维平面的最小曼哈顿距离生成树。

关于二维平面最小曼哈顿距离生成树。感兴趣的可以参考点击打开链接

这样只要顺着树边计算一次就ok了。可以证明时间复杂度为n*sqrt(n)这个我不会证明。

但是这种方法编程复杂度稍微高了一点。所以有一个比较优雅的替代品。那就是先对序列分块。然后对于所有询问按照L所在块的大小排序。如果一样再按照R排序。然后按照排序后的顺序计算。为什么这样计算就可以降低复杂度呢。

一、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时间复杂度是n^1.5。
二、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一部分时间复杂度是n^1.5
三、i与i+1在同一块内时变化不超过n^0.5,跨越一块也不会超过2*n^0.5,不妨看作是n^0.5。由于有n个数,所以时间复杂度是n^1.5
于是就变成了O(n^1.5)了。

正如上面所说 复杂度是O(n^1.5)

理解定义..好像不难的样子?

那我们就直接进入打模板环节x

 

当然,人话题意就是询问在一个数列中询问出现次数等于他本身大小的数有几个

我们看这道题,首先他不要求强制在线,符合莫队的第一个要求

然后 对于每一个区间[L,R],很显然我们用O(1)就可以知道[L+1,R]或[L,R+1]的答案,符合第二个要求

然后再看数据范围.. 哇 1000000 那正解显然不是莫队,但是我们可以用莫队得到70分

代码如下

 

#pragma GCC optimize("O2")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<set>
#include<map>
#include<limits.h>
#include<ctime>
#define N 2000001
typedef long long ll;
const int inf=0x3fffffff;
const int maxn=2017;
using namespace std;
inline int read()
{
    int f=1,x=0;char ch=getchar();
    while(ch>'9'|ch<'0')
    {
        if(ch=='-')
        f=-1;
        ch=getchar();
    }
    while(ch<='9'&&ch>='0')
    {
        x=(x<<3)+(x<<1)+ch-'0';
        ch=getchar();
    }
    return f*x;
}
struct tsdl{
int l,r,block,id;
}a[N];
ll cnt[N],b[N],res[N];
int m,n;
bool cmp(tsdl a,tsdl b)
{
	if(a.block==b.block)
	return a.r<b.r;
	return a.block<b.block;
}
void solve()
{
	sort(a+1,a+m+1,cmp);
	int l=1,r=0;
	ll now=0;
	for(int i=1;i<=m;i++)
	{
		ll ans=0;
		while(l<a[i].l)
		{
			cnt[b[l]]--;//端点的访问次数
			if(cnt[b[l]]==b[l]-1)now--;//如果自减之前符合条件,说明现在不符合
			else if(cnt[b[l]]==b[l])now++;//现在符合
			l++;//注意自加运算的顺序
		}
		while(l>a[i].l)
		{
			l--;
			cnt[b[l]]++;
			if(cnt[b[l]]==b[l]+1)now--;
			else if(cnt[b[l]]==b[l])now++;
		}
		while(r<a[i].r)
		{
			r++;
			cnt[b[r]]++;
			if(cnt[b[r]]==b[r]+1)now--;
			else if(cnt[b[r]]==b[r])now++;
		}
	
		while(r>a[i].r)
		{
			cnt[b[r]]--;
		        if(cnt[b[r]]==b[r])now++;
			else if(cnt[b[r]]==b[r]-1)now--;
			r--;
		}
		res[a[i].id]=now;
	}
}
int main()
{
	n=read();m=read();
	int size=sqrt(n);
	for(int i=1;i<=n;i++)
	b[i]=read();
	for(int i=1;i<=m;i++)
	{
		a[i].l=read(),a[i].r=read();
		a[i].id=i;
		a[i].block=(a[i].l-1)/size+1;
	}
	solve();
	for(int i=1;i<=m;i++)
	printf("%lld\n",res[i]);
}

 

 注意不要在莫队的solve里加一些奇怪的操作,(就像我在考试里没有处理now,是对于每一次询问暴力对所有点扫是否符合......

于是就退化成了O(N1.5M)的垃圾复杂度....朴素暴力才O(NM)...

我太弱啦!

posted @ 2017-09-13 15:07  a799091501  阅读(290)  评论(0编辑  收藏  举报