学了假莫队有感//2018/6/30

     从没有见过那个算法的模板题都这么烦的…………

     https://www.luogu.org/problemnew/show/P1494

    再引荐一片不错的blog,本人就是从哪里学的,大概一个多小时就理解了。

    https://www.cnblogs.com/Paul-Guderian/p/6933799.html

    因为本人对指针的操作并不是非常的熟悉,所以学习莫队也是花了一番心思的。

    记得在上理论课的时候,老师有说过,莫队就是优雅的暴力,所以就特别适合我这种什么都不会(而且学的还都是假的)的蒟蒻了(虽然学的莫队也是假的)。

    莫队的算法运用了一些分块的思想,简而言之,就是通过对若干个问题的分块,排序,然后大幅度的缩小指针移动的次数,而不是对于每个询问都在【l,r】的区间内扫一次,通过空间换时间。

    具体问题具体分析吧

   先上代码(应该比开头提到的blog中的清楚一点,但基本思想是一样的)

    
    再补一句,这个GCD真是非常爽脆

/*
  by Wow_Goodjob;
  2018/6/29;
  小z的袜子:score:100;
*/
#include <bits/stdc++.h>
using namespace std;

#define C getchar()
#define maxn 50010
#define LL long long

inline LL read()
{
	LL x=0;
	char ch;
	bool flag=true;
	while(!isdigit(ch))
	{
		if(ch=='-')
		  flag=false;
		ch=C;
	}
	while(isdigit(ch))
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=C;
	}
	return flag?x:-x;
}

LL n,m;
struct ques
{
	LL l;
	LL r;
	LL id;
	long long A;
	long long B;
}q[maxn]={};
LL bct;
LL sock[maxn]={};
LL block[maxn]={};
LL ans;
LL sum[maxn]={};

bool rule1(ques a,ques b)
{
	return block[a.l]==block[a.r]?a.r<b.r:a.l<b.l;
}

bool rule2(ques a,ques b)
{
	return a.id<b.id;
}

LL gcd(LL a,LL b)
{
	while(b^=a^=b^=a%=b);
	return a;
}

long long sqare(LL a)
{
	return a*a;
}

void revise(LL x,LL add)
{
	ans-=sqare(sum[sock[x]]),sum[sock[x]]+=add,ans+=sqare(sum[sock[x]]);	
}

void LLt()
{
	n=read();
	m=read();
	bct=sqrt(n);
	for(LL i=1;i<=n;i++)
	{
		sock[i]=read();
		block[i]=i/bct+1;
	}
	for(LL i=1;i<=m;i++)
	{
		q[i].l=read();
		q[i].r=read();
		q[i].id=i;
	}
	sort(q+1,q+1+m,rule1);
}

void work()
{
	LL l=1,r=0;
	for(LL i=1;i<=m;i++)
	{
		while(l<q[i].l)  revise(l,-1),l++;
		while(l>q[i].l)  revise(l-1,1),l--;
		while(r<q[i].r)  revise(r+1,1),r++;
		while(r>q[i].r)  revise(r,-1),r--;
		
		if(q[i].l==q[i].r)
		{
			q[i].A=0;
			q[i].B=1;
			continue;
		}
		q[i].A=ans-(q[i].r-q[i].l+1);
		q[i].B=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
		LL ji=gcd(q[i].A,q[i].B);
		q[i].A/=ji;
		q[i].B/=ji;
	}
	sort(q+1,q+1+m,rule2);
}

int main()
{
	LLt();
	work();
	for(LL i=1;i<=m;i++)
	   printf("%lld/%lld\n",q[i].A,q[i].B);
	return 0;
}

    在这一个算法中,首先通过了block的思想将询问分成了sqrt(n)个板块,通过其右端点排序,如右端点一致,就按左端点排序。这样子处理过后再通过离散化的思想将答案重新排列,再一次性输出,就得到了我们想要的答案。

 问题分析

 不难发现,每一种袜子成对出现的概率为(1+2+3+4+……+本颜色袜子的指数(设为n)),即(1+n)*n/2;

 而总的情况数便是(1+区间内袜子的数量)*区间内袜子的数量/2;

 所以,我们只要解决每种袜子出现的次数便好了。

时间复杂度分析(由于本人太菜,只好抄人家的)

首先,枚举m个答案,就一个m了。设分块大小为unit。
①l的移动:若下一个询问与当前询问的l所在的块不同,那么只需要经过最多2*unit步可以使得l成功到达目标.复杂度为:O(m*unit)
②r的移动:r只有在Be[l]相同时才会有序(其余时候还是疯狂地乱跳,你知道,一提到乱跳,那么每一次最坏就要跳n次!),Be[l]什么时候相同?在同一块里面l就Be[]相同。对于每一个块,排序执行了第二关键字:r。所以这里面的r是单调递增的,所以枚举完一个块,r最多移动n次。总共有n/unit个块:复杂度为:O(n*n/unit)
总结:O(n*unit+n*n/unit)(n,m同级,就统一使用n)
根据基本不等式得:当n为sqrt(n)时,得到莫队算法的真正复杂度:
O(n*sqrt(n))

当然,莫队也是有他的不足之处的

那就是:他是离线的,他是离线的,他是离线的!!!

所以当我们碰到强制在线的题目的时候,就只能hehe了。

(写的较水,dalao勿喷)

 

点赞