P1494 [国家集训队] 小 Z 的袜子 查询莫队
解题思路
这是一个典型的莫队算法问题,用于处理多个区间查询。题目要求计算在给定区间内随机取两只袜子颜色相同的概率。解题步骤如下:
-
莫队算法框架:将查询分块排序,通过移动区间指针来高效处理多个查询
-
组合数计算:对于每种颜色,计算从中取两只袜子的组合数
-
概率计算:统计区间内所有颜色组合数的和,与总可能数相比得到概率
-
分数化简:将概率分数化为最简形式
-
特判处理:单独处理区间长度为1的特殊情况
四个while中的sum公式解析
在这段莫队算法的实现中,sum变量用于维护当前区间内所有颜色可以组成相同颜色袜子的对数。具体来说,sum的计算基于组合数学中的组合数公式。
关键公式解释
对于每种颜色,如果有s只袜子,那么从中选出2只相同颜色袜子的组合数为:
C(s, 2) = s*(s-1)/2
因此:
-
总对数计算:当前区间的总相同颜色袜子对数就是将所有颜色的
C(s, 2)相加:
sum = Σ [s[a[i]]*(s[a[i]]-1)/2] 对所有颜色a[i]
动态维护sum:
-
-
当增加一只颜色为
c的袜子时:-
首先减去旧的组合数:
sum -= s[c]*(s[c]-1)/2 -
然后
s[c]++(数量增加) -
最后加上新的组合数:
sum += s[c]*(s[c]-1)/2
-
-
当减少一只颜色为
c的袜子时:-
首先减去旧的组合数:
sum -= s[c]*(s[c]-1)/2 -
然后
s[c]--(数量减少) -
最后加上新的组合数:
sum += s[c]*(s[c]-1)/2
-
-
代码注释
#include<bits/stdc++.h> #define ll long long // 定义long long为ll,方便使用 using namespace std; const int N = 1e5 + 10; // 定义最大数据范围 // 查询结构体:存储每个查询的左右端点和查询编号 struct node{ int l,r,id; }; node t[N]; // 存储所有查询 // 变量定义 ll n,m,L = 1,R,bcnt,sum; // n袜子数,m查询数,L/R当前区间指针,bcnt块大小,sum当前组合数和 ll a[N],cnt[N],ans1[N],ans2[N]; // a袜子颜色,cnt颜色计数,ans1/ans2答案分子分母 // 计算最大公约数 ll gcd(ll a,ll b) { if(b == 0) return a; else return gcd(b,a % b); } // 查询排序比较函数 bool cmp(node a,node b) { // 按块排序,同一块内按右端点排序 if(a.l / bcnt == b.l / bcnt) return a.r < b.r; else return a.l < b.l; } // 添加位置pos的袜子到当前区间 void add(int pos) { // 先减去该颜色原有的组合数 sum -= 1ll * cnt[a[pos]] * (cnt[a[pos]] - 1) / 2; // 增加该颜色计数 cnt[a[pos]]++; // 加上新的组合数 sum += 1ll * cnt[a[pos]] * (cnt[a[pos]] - 1) / 2; } // 从当前区间移除位置pos的袜子 void del(int pos) { // 先减去该颜色原有的组合数 sum -= 1ll * cnt[a[pos]] * (cnt[a[pos]] - 1) / 2; // 减少该颜色计数 cnt[a[pos]]--; // 加上新的组合数 sum += 1ll * cnt[a[pos]] * (cnt[a[pos]] - 1) / 2; } int main() { // 输入数据 scanf("%d%d",&n,&m); bcnt = sqrt(n); // 计算块大小 // 输入袜子颜色 for(int i = 1; i <= n; i++) scanf("%d",&a[i]); // 输入查询 for(int i = 1; i <= m; i++) { scanf("%d%d",&t[i].l,&t[i].r); t[i].id = i; // 记录查询编号 } // 对查询进行排序 sort(t + 1,t + 1 + m, cmp); // 处理每个查询 for(int i = 1; i <= m; i++) { int tl = t[i].l, tr = t[i].r; // 调整区间指针L while(L < tl) del(L),L++; // 左指针右移,删除左边元素 while(L > tl) L--,add(L); // 左指针左移,添加左边元素 // 调整区间指针R while(R < tr) R++,add(R); // 右指针右移,添加右边元素 while(R > tr) del(R),R--; // 右指针左移,删除右边元素 // 处理答案 if(tl == tr) { // 区间长度为1的特殊情况 ans1[t[i].id] = 0; ans2[t[i].id] = 1; } else { // 计算区间长度和总组合数 ll len = tr - tl + 1; ll tot = len * (len - 1) / 2; // 计算最大公约数并约分 ll g = gcd(sum,tot); ans1[t[i].id] = sum / g; ans2[t[i].id] = tot / g; } } // 输出结果 for(int i = 1; i <= m; i++) printf("%lld/%lld\n",ans1[i],ans2[i]); return 0; }

浙公网安备 33010602011771号