从没有见过那个算法的模板题都这么烦的…………
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勿喷)