用莫队维护当前区间内同色袜子对数量,再与总二元组数量约分得到概率。
OJ: luogu
题目 ID: P1494
难度:普及+/提高
标签:莫队离线数据结构
日期: 2026-06-22 23:14
题意
给定一排袜子的颜色。每个询问 [l,r] 中随机选两只袜子,求两只颜色相同的概率,输出约分后的分数。
思路
如果已知区间内每种颜色的出现次数 cnt[c],那么同色选择方案数是:
text
sum cnt[c] * (cnt[c] - 1) / 2总选择方案数是:
text
len * (len - 1) / 2朴素做法是每个询问重新扫描区间统计颜色。
先看一个可以直接验证想法的朴素解:
cpp
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:每个询问直接统计颜色次数并计算概率,只适合小数据。
const int MAXN = 505;
int n, m;
int color[MAXN];
long long cnt[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> color[i];
}
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
for (int c = 0; c <= n; c++) {
cnt[c] = 0;
}
for (int j = l; j <= r; j++) {
cnt[color[j]]++;
}
long long same = 0;
long long len = r - l + 1;
for (int c = 1; c <= n; c++) {
same += cnt[c] * (cnt[c] - 1) / 2;
}
if (len == 1) {
cout << "0/1\n";
} else {
long long total = len * (len - 1) / 2;
long long g = gcd(same, total);
cout << same / g << '/' << total / g << '\n';
}
}
return 0;
}要处理很多区间询问,可以用莫队。维护当前区间 [cur_l, cur_r],以及当前区间内同色袜子对数量 same_pair_count。
移动端点时只会加入或删除一只袜子:
- 加入颜色
c:它能和已有cnt[c]只同色袜子组成新同色对,所以same_pair_count += cnt[c]; - 删除颜色
c:删除后剩余cnt[c]只同色袜子,少掉这些配对,所以same_pair_count -= cnt[c]。
莫队排序后,左右端点移动总量较小。每个询问调整完区间后,用 same_pair_count / C(len,2) 约分输出即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
struct Query {
int l, r, id;
};
int n, m;
int block_size;
int color[MAXN];
long long color_count[MAXN]; // 当前区间内每种颜色出现次数。
long long same_pair_count; // 当前区间内同色无序对数量之和。
long long answer_num[MAXN], answer_den[MAXN];
Query queries[MAXN];
bool cmp_query(const Query &a, const Query &b) {
int block_a = a.l / block_size;
int block_b = b.l / block_size;
if (block_a != block_b) {
return block_a < block_b;
}
if (block_a & 1) {
return a.r > b.r;
}
return a.r < b.r;
}
void add_pos(int pos) {
int c = color[pos];
// 新加入一个颜色 c,会和已有 color_count[c] 只同色袜子配成同色对。
same_pair_count += color_count[c];
color_count[c]++;
}
void remove_pos(int pos) {
int c = color[pos];
color_count[c]--;
same_pair_count -= color_count[c];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> color[i];
}
for (int i = 1; i <= m; i++) {
cin >> queries[i].l >> queries[i].r;
queries[i].id = i;
}
block_size = max(1, (int)sqrt(n));
sort(queries + 1, queries + m + 1, cmp_query);
int cur_l = 1, cur_r = 0;
for (int i = 1; i <= m; i++) {
int l = queries[i].l;
int r = queries[i].r;
while (cur_l > l) {
cur_l--;
add_pos(cur_l);
}
while (cur_r < r) {
cur_r++;
add_pos(cur_r);
}
while (cur_l < l) {
remove_pos(cur_l);
cur_l++;
}
while (cur_r > r) {
remove_pos(cur_r);
cur_r--;
}
long long len = r - l + 1;
if (len == 1) {
answer_num[queries[i].id] = 0;
answer_den[queries[i].id] = 1;
} else {
long long total_pair = len * (len - 1) / 2;
long long g = gcd(same_pair_count, total_pair);
answer_num[queries[i].id] = same_pair_count / g;
answer_den[queries[i].id] = total_pair / g;
}
}
for (int i = 1; i <= m; i++) {
cout << answer_num[i] << '/' << answer_den[i] << '\n';
}
return 0;
}复杂度
时间复杂度约为
空间复杂度
总结
莫队题的关键是设计好“加入一个点”和“删除一个点”对答案的影响。本题维护的是同色二元组数量,而不是直接维护概率。