[ BZOJ 2038 ] Hose

本文深入探讨了莫队算法的原理与实现,通过具体数列问题实例,讲解了如何利用莫队算法高效解决区间概率计算问题。文章详细介绍了算法的优化策略,包括区间移动、询问排序等关键步骤,以及其实现代码。

\(\\\)

Description


给出一个数列,多次询问,每次给出一个区间 \([l_i,r_i]\) ,问在区间中随意选两个位置不同的数,是同一个数的概率有多大。

  • \(n,m\le 5\times 10^4\)

\(\\\)

Solution


莫队模板。

才发现自己还没有正经学过莫队

\(cnt[i]\) 表示 \(i\)\([l,r]\) 中出现的个数,\(S\) 为这个区间的数集,一个区间的答案可以表示成:
\[ \frac{\sum_{x\in S}C_{cnt[x]}^2}{C_{r-l+1}^2} \]
分母好说,考虑如何求分子,不妨展开一下。
\[ \sum_{x\in S}C_{cnt[x]}^2=\sum_{x\in S}\frac{cnt[x]\times(cnt[x]-1)}{2}=\frac 12\times\bigg( \sum_{x\in S}cnt[x]^2-\sum_{x\in S}cnt[x]\bigg) \]
于是有一个机智的做法:我们设 \(ans\) 表示括号里面的东西。

每次区间移动时,假如 \(cnt[x]\to cnt[x]'\) ,那么对 \(ans\) 的更新有:
\[ ans=ans-cnt[x]^2+cnt[x]+cnt[x]'^2-cnt[x]' \]
就可以 \(O(1)\) 移动回答区间的左端点或右端点了。

关于询问的排序:左端点在同一个块内的按照右端点排序,不在同一个块内的按照左端点所在块排序。

这样每个块右端点至多移动 \(O(N)\),左端点至多移动 \(O(q\sqrt N)\) ,其中 \(q\) 是左端点在这个块内的询问个数。

总复杂度 \(O(N\sqrt N)\)

\(\\\)

Code


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 50010
#define R register
#define gc getchar
using namespace std;
typedef long long ll;

inline ll rd(){
  ll x=0; char c=gc();
  while(!isdigit(c)) c=gc();
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return x;
}

ll ans,n,m,s[N],cnt[N],bl[N];

struct Q{ll l,r,id,ans1,ans2;}q[N];

inline bool cmp(Q x,Q y){
  return bl[x.l]==bl[y.l]?x.r<y.r:bl[x.l]<bl[y.l];
}

inline bool cmp1(Q x,Q y){return x.id<y.id;}

inline ll C(ll x){return (x*(x-1)>>1);}

ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}

inline void move(ll &llst,ll &rlst,ll lnew,ll rnew){
  while(llst<lnew){
    int x=cnt[s[llst]];
    ans=ans-x*x+(x-1)*(x-1)+1;
    --cnt[s[llst]]; ++llst;
  }
  while(llst>lnew){
    --llst;
    int x=cnt[s[llst]];
    ans=ans-x*x+(x+1)*(x+1)-1;
    ++cnt[s[llst]];
  }
  while(rlst<rnew){
    ++rlst;
    int x=cnt[s[rlst]];
    ans=ans-x*x+(x+1)*(x+1)-1;
    ++cnt[s[rlst]];
  }
  while(rlst>rnew){
    int x=cnt[s[rlst]];
    ans=ans-x*x+(x-1)*(x-1)+1;
    --cnt[s[rlst]]; --rlst;
  }
}

int main(){
  ll n=rd(),m=rd();
  ll t=sqrt(n);
  for(R ll i=1,cntt=1;i<=n;++i){
    if(i%t==0) ++cntt;
    bl[i]=cntt;
  }
  for(R ll i=1;i<=n;++i) s[i]=rd();
  for(R ll i=1;i<=m;++i){q[i].l=rd();q[i].r=rd();q[i].id=i;}
  sort(q+1,q+1+m,cmp);
  ll lnow=1,rnow=1;
  ++cnt[s[1]];
  for(R ll i=1,g;i<=m;++i){
    if(q[i].l==q[i].r){
      q[i].ans1=0;
      q[i].ans2=1;
      continue;
    }
    move(lnow,rnow,q[i].l,q[i].r);
    g=gcd(ans/2,C(q[i].r-q[i].l+1));
    q[i].ans1=ans/g/2;
    q[i].ans2=C(q[i].r-q[i].l+1)/g;
  }
  sort(q+1,q+1+m,cmp1);
  for(R int i=1;i<=m;++i) printf("%lld/%lld\n",q[i].ans1,q[i].ans2);
  return 0;
}

转载于:https://www.cnblogs.com/SGCollin/p/9991191.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值