解题思路:
莫队出的模板题。
如果我们知道了询问区间中每种颜色的数量 cnti ,那么一种颜色的贡献就是 C2cnti ,总方案数是 C2r−l+1 ,每种颜色贡献求和再与总方案数求gcd即可。
关键是如何快速统计区间内每种颜色的数量,这就要用到莫队算法。
考虑建立两个指针l,r,表示区间[l,r]内每种颜色的数量已知。
再将询问离线,按询问左端点所在块(块大小为
n√
)为第一关键字,右端点坐标为第二关键字排序,每次询问一位位暴力挪动l,r指针到该询问左右端点对应位置,同时修改贡献即可(挪一次指针只会改变一种颜色的数量,直接减去原来贡献,加上新贡献),这就是莫队算法,可以证明指针挪动次数为
O(nn√)
,证明如下:
先考虑左指针。
当询问左端点在同一块时,每次挪动不超过
n√
次;换块时,最多移动
2n√
次,所以左指针移动次数是
O(nn√)
的。
再考虑右指针。
当询问左端点在同一块时,询问右端点单调递增,一块内最多移动n次;当换块时,右指针最多从n挪回1,也是n次;而整个区间最多会被分为
n√
块,所以右端点移动次数也是
O(nn√)
的。
综上,莫队算法的时间复杂度是 O(nn√) 的。
其实还有带修改的莫队,可以做做bzoj2120,
题解详见http://blog.youkuaiyun.com/cdsszjj/article/details/78397256
#include<bits/stdc++.h>
using namespace std;
int read()
{
int i=0,f=1;char c;
for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
if(c=='-')f=-1,c=getchar();
for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
return i*f;
}
const int N=50005;
int n,m,S,a[N],cnt[N];
struct node
{
int l,r,id,block;
inline friend bool operator < (const node &a,const node &b)
{return a.block<b.block||a.block==b.block&&a.r<b.r;}
}q[N];
pair<int,int>ans[N];
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
int calc(int x)
{
return 1ll*x*(x-1)/2;
}
int main()
{
//freopen("lx.in","r",stdin);
n=read(),m=read(),S=sqrt(n);
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=m;i++)
{
q[i].l=read(),q[i].r=read(),q[i].id=i;
q[i].block=(q[i].l-1)/S+1;
}
sort(q+1,q+m+1);
int l=1,r=0,num=0;
for(int i=1;i<=m;i++)
{
int tot=calc(q[i].r-q[i].l+1);
while(l<q[i].l)num=num-calc(cnt[a[l]])+calc(--cnt[a[l]]),++l;
while(l>q[i].l)--l,num=num-calc(cnt[a[l]])+calc(++cnt[a[l]]);
while(r<q[i].r)++r,num=num-calc(cnt[a[r]])+calc(++cnt[a[r]]);
while(r>q[i].r)num=num-calc(cnt[a[r]])+calc(--cnt[a[r]]),--r;
int d=gcd(num,tot);
ans[q[i].id]=make_pair(num/d,tot/d);
}
for(int i=1;i<=m;i++)
cout<<ans[i].first<<'/'<<ans[i].second<<'\n';
return 0;
}

本文介绍了一道经典的莫队算法模板题,并详细解析了如何使用莫队算法快速统计区间内每种颜色的数量,进而计算出总方案数。通过合理安排指针移动策略,实现了时间复杂度为O(n√n)的高效解法。
575

被折叠的 条评论
为什么被折叠?



