bzoj 2038 小Z的袜子 莫队算法

  题意

    给你一个长度序列,有多组询问,每次询问(l,r)任选两个数相同的概率。n <= 50000,数小于等于n。

  莫队算法裸题。

  莫队算法:将序列分为根号n段,将询问排序,以L所在的块为第一关键字,R为第二关键字排序,以次处理询问O(n^1.5)

  由于是按L所在的块为第一关键字、R为第二关键字排序的,所以在每块内L的变化最多为n,总O(n^1.5);R在每块内递增,每块内变化最多为n,总O(n^1.5),故O(n^1.5)。

  具体可以抽象为二维的点来理解。

  概率p = sigma(c[i]*(c[i]-1))/(r-l+1) = (sigma(c[i]*c[i])-(r-l+1))/(r-l+1),c[i]为区间中数i的个数,只需要维护sigma(c[i]*c[i])即可。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <algorithm>
#include <iostream>
#include <cmath>

using namespace std;

typedef long long LL;
const int maxn = 50005;
int n, m, a[maxn], pos[maxn];
LL s[maxn], ansA[maxn], ansB[maxn], sum;

struct Query{
  int l, r, id;
  Query(int l = 0, int r = 0, int id = 0):
      l(l), r(r), id(id) {}
  bool operator < (const Query &AI) const{
    if (pos[l] == pos[AI.l])
      return r < AI.r;
    return pos[l] < pos[AI.l];
  }
}b[maxn];

void update(int p, LL d){
  sum -= s[p]*s[p];
  s[p] += d;
  sum += s[p]*s[p];
}

LL gcd(LL x, LL y){
  if (y == 0)
    return x;
  return gcd(y, x%y);
}

void work(){
  for (int i = 1, l = 1, r = 0; i <= m; ++i){
    for (; l < b[i].l; ++l)
      update(a[l], -1LL);
    for (; l > b[i].l; --l)
      update(a[l-1], 1LL);
    for (; r < b[i].r; ++r)
      update(a[r+1], 1LL);
    for (; r > b[i].r; --r)
      update(a[r], -1LL);
    if (l == r){
      ansA[b[i].id] = 0, ansB[b[i].id] = 1;
      continue ;
    }
    ansA[b[i].id] = sum-(r-l+1);
    ansB[b[i].id] = LL(r-l+1)*LL(r-l);
    LL k = gcd(ansA[b[i].id], ansB[b[i].id]);
    ansA[b[i].id] /= k, ansB[b[i].id] /= k;
  }
}

int main(){
  scanf("%d %d", &n, &m);
  for (int i = 1; i <= n; ++i)
    scanf("%d", &a[i]);
  for (int i = 1; i <= m; ++i)
    scanf("%d %d", &b[i].l, &b[i].r), b[i].id = i;
  int block = int(sqrt(n));
  for (int i = 1; i <= n; ++i)
    pos[i] = (i-1)/block+1;
  sort(b+1, b+m+1);
  work();
  for (int i = 1; i <= m; ++i)
    printf("%lld/%lld
", ansA[i], ansB[i]);
  return 0;
}
原文地址:https://www.cnblogs.com/-ZZB-/p/6403606.html