题目链接: 2038: [2009国家集训队]小Z的袜子(hose)
题目大意
一个长度为n的序列, 每个点有一个颜色, 用col[i]表示, q次询问, 每次求出[L, R]区间中随机选两个点的颜色相同的概率
思路
对于[L, R], 设区间内各种颜色的数量为
所以, 我们需要做的是快速求出区间内所有颜色数量的平方和
因为无法用线段树一类的数据结构进行处理, 所以需要用到传说中的莫队算法(Mo’s Algorithm)
莫队算法
莫队算法可以高效处理这类对大量区间进行查询, 通过对所有查询的区间进行离线排序处理, 使排序能够根据上一次询问的答案快速求出下一次询问的答案, 使得整体复杂度大大下降
如果知道了区间[L, R]的情况, 我们可以快速(
莫队算法的核心就是如何得到最优的处理顺序
一种简单但是复杂度略高一点的方法:
对于整个区间[1, n], 我们先将其分成
代码
Accepted 1472ms 3456kB C++
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 5e4 + 100;
typedef long long ll;
int n, m, block[maxn], col[maxn];
ll sum[maxn], ans;//sum[i]存储当前区间颜色i的数量, ans表示当前区间各个颜色数量和
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
struct Query
{
int l, r, id;
ll nume, deno;//答案分子分母
} querys[maxn];
bool cmp(const Query & x, const Query & y)
{
if (block[x.l] == block[y.l]) return x.r < y.r;
return x.l < y.l;
}
bool cmp_id(const Query & x, const Query & y)
{
return x.id < y.id;
}
void update(int p, int add)//区间向相邻区间移动, 更新答案
{
int c = col[p];
ans -= sum[c] * sum[c];
sum[c] += add;
ans += sum[c] * sum[c];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", col + i);
int block_size = (int)sqrt(n);
for (int i = 1; i <= n; ++i) block[i] = (i - 1) / block_size + 1;
for (int i = 1; i <= m; ++i)
{
scanf("%d%d", &querys[i].l, &querys[i].r);
querys[i].id = i;
}
sort(querys + 1, querys + 1 + m, cmp);
int l = 1, r = 0;//最初为一个空区间[1, 0], ans(颜色数量平方和) = 0
for (int i = 1; i <= m; ++i)
{
//更新区间, 从上一个区间得到下一个区间的情况
for (; r < querys[i].r; ++r) update(r + 1, 1);
for (; r > querys[i].r; --r) update(r, -1);
for (; l < querys[i].l; ++l) update(l, -1);
for (; l > querys[i].l; --l) update(l - 1, 1);
//计算答案并化简分数
if (querys[i].l == querys[i].r)
{
querys[i].nume = 0;
querys[i].deno = 1;
continue;
}
ll len = querys[i].r - querys[i].l + 1;
querys[i].nume = ans - len;
querys[i].deno = len * (len - 1);
ll t = gcd(querys[i].nume, querys[i].deno);
querys[i].nume /= t;
querys[i].deno /= t;
}
//输出答案
sort(querys + 1, querys + 1 + m, cmp_id);
for (int i = 1; i <= m; ++i) printf("%lld/%lld\n", querys[i].nume, querys[i].deno);
return 0;
}