小明选袜子 / 小Z的袜子
题目链接:ybt金牌导航6-4-4 / luogu P1494
题目大意
有一排颜色,然后多组询问每次给你区间,问你这个区间中随便选两个,是同一个颜色的概率。
思路
其实就是普通的莫队?
我们考虑某个颜色多了一个和少了一个的贡献要如何计算。
首先不难想到如果一个区间的长度是 n n n,每个颜色出现的个数为 x i x_i xi,那贡献就是 ∑ ( x i ( x i − 1 ) / 2 ) n ( n − 1 ) / 2 = ∑ x i ( x i − 1 ) n ( n − 1 ) \dfrac{\sum(x_i(x_i-1)/2)}{n(n-1)/2}=\dfrac{\sum x_i(x_i-1)}{n(n-1)} n(n−1)/2∑(xi(xi−1)/2)=n(n−1)∑xi(xi−1)
那我们可以只维护上面的部分,如果
x
i
x_i
xi 变成
x
i
+
1
x_i+1
xi+1,它的贡献就由
x
i
(
x
i
−
1
)
x_i(x_i-1)
xi(xi−1) 变成
(
x
i
+
1
)
x
i
(x_i+1)x_i
(xi+1)xi,多了
2
x
i
2x_i
2xi。
同理,如果
x
i
x_i
xi 变成
x
i
−
1
x_i-1
xi−1,它的贡献就由
x
i
(
x
i
−
1
)
x_i(x_i-1)
xi(xi−1) 变成
(
x
i
−
1
)
(
x
i
−
2
)
(x_i-1)(x_i-2)
(xi−1)(xi−2),少了
2
x
i
2x_i
2xi。
然后这么维护就可以啦。
代码
#include<cmath>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
struct node {
int l, r, pl;
}q[50001];
struct answer {
ll fm, fz;
}an[50001];
int n, m, c[50001], sz, l, r;
int bl[50001], num[50001];
bool in[50001];
ll ans;
bool cmp(node x, node y) {
if (bl[x.l] != bl[y.l]) return bl[x.l] < bl[y.l];
return x.r < y.r;
}
ll gcd(ll x, ll y) {
if (!y) return x;
return gcd(y, x % y);
}
void ck(answer &x) {
ll tmp = gcd(x.fz, x.fm);
x.fz /= tmp; x.fm /= tmp;
}
void change(int pl) {
if (in[pl]) {
num[c[pl]]--;
ans -= 2ll * num[c[pl]];
}
else {
ans += 2ll * num[c[pl]];
num[c[pl]]++;
}
in[pl] ^= 1;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
sz = sqrt(n);
for (int i = 1; i <= n; i++) bl[i] = (i - 1) / sz + 1;
for (int i = 1; i <= m; i++) {
scanf("%d %d", &q[i].l, &q[i].r);
q[i].pl = i;
}
sort(q + 1, q + m + 1, cmp);
l = 1; r = 0; ans = 0;
for (int i = 1; i <= m; i++) {
while (l < q[i].l) change(l), l++;
while (l > q[i].l) l--, change(l);
while (r < q[i].r) r++, change(r);
while (r > q[i].r) change(r), r--;
an[q[i].pl].fz = ans;
an[q[i].pl].fm = 1ll * (q[i].r - q[i].l) * (q[i].r - q[i].l + 1);
// if (l == r) an[q[i].pl] = (answer){1, 0};//luogu上有这句话,一本通上没有
ck(an[q[i].pl]);
}
for (int i = 1; i <= m; i++)
printf("%lld/%lld\n", an[i].fz, an[i].fm);
return 0;
}