bzoj2038 [2009国家集训队]小Z的袜子(hose)
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=2038
题意:
小Z把N只袜子从1到N编号,每只袜子有颜色C,然后从编号L到R中任意抽取两只袜子,你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问M个(L,R)以方便自己选择。
数据范围
N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。
题解:
(我的第一道莫队)
莫队裸题。
要开long long。
初始化如果l,r都设成0,要注意cnt[0]设为1,或者l=1,r=0。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
using namespace std;
const int N=50010;
int n,m,s,col[N],cnt[N];
LL sum,ans[N][2];
int bel(int x)
{
return (x-1)/s+1;
}
struct node
{
int L,R,note;
node(){}
node(int L,int R,int note) : L(L),R(R),note(note) {}
}q[N];
bool cmp(const node &A,const node &B)
{
if(bel(A.L)!=bel(B.L)) return A.L<B.L;
else return A.R<B.R;
}
void modify(int x,int val)
{
sum-=1LL*cnt[col[x]]*1LL*(cnt[col[x]]-1);
cnt[col[x]]+=val;
sum+=1LL*cnt[col[x]]*1LL*(cnt[col[x]]-1);
}
LL gcd(LL x,LL y)
{
return (y==0)?x:gcd(y,x%y);
}
void simp(LL x)
{
if(ans[x][0]==0) {ans[x][1]=1;return;}
LL g=gcd(ans[x][0],ans[x][1]);
ans[x][0]/=g; ans[x][1]/=g;
return;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&col[i]);
s=sqrt(n);
for(int i=1;i<=m;i++)
{
int L,R;
scanf("%d%d",&L,&R);
q[i]=node(L,R,i);
}
sort(q+1,q+m+1,cmp);
int lf=1; int rg=0; //!!!
sum=0;
for(int i=1;i<=m;i++)
{
int L=q[i].L; int R=q[i].R; int note=q[i].note;
while(rg<R) modify(++rg,1);
while(rg>R) modify(rg--,-1);
while(lf>L) modify(--lf,1);
while(lf<L) modify(lf++,-1);
ans[note][0]=1LL*sum;
ans[note][1]=1LL*(R-L+1)*1LL*(R-L);
}
for(int i=1;i<=m;i++)
{
simp(i);
printf("%I64d/%I64d\n",ans[i][0],ans[i][1]);
}
return 0;
}
引用下关于莫队的复杂度证明:
假设我们每k个点分一块。
如果当前询问与上一询问左端点处在同一块,那么左端点移动为O(k)。虽然右端点移动可能高达O(n),但是整一块询问的右端点移动距离之和也是O(n)(想一想,为什么)。因此平摊意义下,整块移动为O(k) × O(k) + O(n),一共有n / k块,时间复杂度为O(kn + n2 / k)。
总的移动次数为O(kn + n2 / k)。因此,当k = √n时,运行时间上界最优,为O( n1.5 )。
最后,因此根据每次insert和erase的时间复杂度,乘上O(1)或者O(logn)亦或O(n)不等,得到完整算法的时间复杂度。