【BZOJ2038】【集训队2009】小Z的袜子(分块+莫队)

题目描述

传送门

题解

首先求概率的方法为:假设在LR这段区间里颜色为 x,y,z 的袜子分别有 a,b,c 个。
那么概率(利用排列组合推导)
p=a(a1)2+b(b1)2+c(c1)2+(RL+1)(RL)2
=a2+b2+c2+(a+b+c+)(RL+1)(RL)
a2+b2+c2+(RL+1)(RL+1)(RL)
那么我们用莫队算法+分块乱搞就能求出答案。 O(nn)
首先离线操作将区间排序,按照左端点的分块的编号,同一块内按照右端点排序。然后暴力更改区间求值即可。

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LL long long

const int max_n=5e4+5;
const int max_m=5e4+5;
const int max_c=5e4+5;

struct hp{
    LL l,r,block,num;
}f[max_m];
struct hq{ LL up,down; }ans[max_m];
LL a[max_n],c[max_c];
LL n,nn,m,L,R,up,down,GCD,upans,downans;

inline int cmp(hp a,hp b){
    return a.block<b.block||(a.block==b.block&&a.r<b.r)||(a.block==b.block&&a.r==b.r&&a.l<b.l);
}

inline LL gcd(LL a,LL b){
    if (!b) return a;
    else return gcd(b,a%b);
}

int main(){
    scanf("%lld%lld",&n,&m);
    nn=sqrt(n);
    for (int i=1;i<=n;++i)
      scanf("%lld",&a[i]);
    for (int i=1;i<=m;++i){
        scanf("%lld%lld",&f[i].l,&f[i].r);
        f[i].num=i; 
    }
    for (int i=1;i<=m;++i){
        if (f[i].l%nn==0)
          f[i].block=f[i].l/nn;
        else f[i].block=f[i].l/nn+1;
    }
    sort(f+1,f+m+1,cmp);
    L=R=1;
    c[a[1]]=1;
    up=0;
    for (int i=1;i<=m;++i){
        up+=R-L+1;

        if (f[i].l<L)
          for (int j=L-1;j>=f[i].l;--j){
            up-=c[a[j]]*c[a[j]];
            up+=(++c[a[j]])*c[a[j]];
          }
        if (f[i].r>R)
          for (int j=R+1;j<=f[i].r;++j){
            up-=c[a[j]]*c[a[j]];
            up+=(++c[a[j]])*c[a[j]];
          }
        if (f[i].l>L)
          for (int j=L;j<f[i].l;++j){
            up-=c[a[j]]*c[a[j]];
            up+=(--c[a[j]])*c[a[j]];
          }
        if (f[i].r<R)
          for (int j=R;j>f[i].r;--j){
            up-=c[a[j]]*c[a[j]];
              up+=(--c[a[j]])*c[a[j]];
          }

        L=f[i].l,R=f[i].r;
        up-=R-L+1;
        down=(R-L+1)*(R-L);
        if (!up){
            ans[f[i].num].up=0;
            ans[f[i].num].down=1;
            continue;
        }
        GCD=gcd(up,down);
        upans=up/GCD;
        downans=down/GCD;
        ans[f[i].num].up=upans;
        ans[f[i].num].down=downans;
    }
    for (int i=1;i<=m;++i)
      printf("%lld/%lld\n",ans[i].up,ans[i].down);
}

你可能感兴趣的:(分块,bzoj,集训队,莫队)