【bzoj2038】国家集训队 小Z的袜子[莫队]
国家集训队 小Z的袜子
已知区间\([l,r]\)中袜子出现次数为\(a,b,c...\)
则为\((a*(a-1)/2+b*(b-1)+c*(c-1)/2..)/((r-l+1)*(r-l)/2)\)
\(=(a^2+b^2+c^2+...+(r-l+1))/((r-l+1)*(r-l))\)
然后就能转化为上一道题辽😀(系统自带表情好可爱啊啊啊啊
排序的一个优化?
排序改成:
bool cmp(node A,node B){return (A.bl^B.bl)?A.bl<B.bl:((A.bl&1)?A.r<B.r:A.r>B.r);}
就快了好多
也就是说,对于左端点在同一奇数块的区间,右端点按升序排列,反之降序。这个东西也是看着没用,但实际效果显著。
它的主要原理便是右指针跳完奇数块往回跳时在同一个方向能顺路把偶数块跳完,然后跳完这个偶数块又能顺带把下一个奇数块跳完。理论上主算法运行时间减半,实际情况有所偏差。
(不过能优化得很爽就对了)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define Max(x,y) ((x)>(y)?(x):(y))
#define Min(x,y) ((x)<(y)?(x):(y))
#define Abs(x) ((x)<0?-(x):(x))
#define ls (o<<1)
#define rs (o<<1|1)
const int N=50000+5,M=100+5,inf=0x3f3f3f3f;
int n,m,block,a[N],cnt[N];
ll Ans,aa[N],ab[N];
template <class t>void rd(t &x){
x=0;int w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=w?-x:x;
}
struct node{int l,r,id,bl;}q[N];
bool cmp(node A,node B){return (A.bl^B.bl)?A.bl<B.bl:((A.bl&1)?A.r<B.r:A.r>B.r);}
//bool cmp(node A,node B){return A.bl==B.bl?A.r<B.r:A.bl<B.bl;}
void count(int x,int add){
Ans-=(cnt[a[x]]*cnt[a[x]]);
cnt[a[x]]+=add;
Ans+=(cnt[a[x]]*cnt[a[x]]);
}
ll gcd(ll a,ll b){
if(a>b) swap(a,b);
return !a?b:gcd(b%a,a);
}
int main(){
freopen("in.txt","r",stdin);
rd(n),rd(m),block=sqrt(n);
for(int i=1;i<=n;++i) rd(a[i]);
for(int i=1;i<=m;++i) rd(q[i].l),rd(q[i].r),q[i].id=i,q[i].bl=(q[i].l-1)/block+1;
sort(q+1,q+m+1,cmp);
int l=1,r=0;Ans=0ll;
for(int i=1;i<=m;++i){
while(l<q[i].l) count(l++,-1);
while(l>q[i].l) count(--l,1);
while(r<q[i].r) count(++r,1);
while(r>q[i].r) count(r--,-1);
if(q[i].l==q[i].r) {aa[q[i].id]=0,ab[q[i].id]=1;continue;}
ll x=q[i].r-q[i].l+1;
aa[q[i].id]=Ans-x,ab[q[i].id]=x*(x-1);
}
for(int i=1;i<=m;++i){
ll x=gcd(aa[i],ab[i]);
if(x>0) printf("%lld/%lld\n",aa[i]/x,ab[i]/x);
else printf("%lld/%lld\n",aa[i],ab[i]);
}
return 0;
}