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\n", ansA[i], ansB[i]); return 0; }
Nothing is impossible!