P1494 [国家集训队] 小 Z 的袜子 查询莫队

解题思路

这是一个典型的莫队算法问题,用于处理多个区间查询。题目要求计算在给定区间内随机取两只袜子颜色相同的概率。解题步骤如下:

  1. 莫队算法框架:将查询分块排序,通过移动区间指针来高效处理多个查询

  2. 组合数计算:对于每种颜色,计算从中取两只袜子的组合数

  3. 概率计算:统计区间内所有颜色组合数的和,与总可能数相比得到概率

  4. 分数化简:将概率分数化为最简形式

  5. 特判处理:单独处理区间长度为1的特殊情况

四个while中的sum公式解析

在这段莫队算法的实现中,sum变量用于维护当前区间内所有颜色可以组成相同颜色袜子的对数。具体来说,sum的计算基于组合数学中的组合数公式。

关键公式解释

对于每种颜色,如果有s只袜子,那么从中选出2只相同颜色袜子的组合数为:

C(s, 2) = s*(s-1)/2

因此:

  1. 总对数计算:当前区间的总相同颜色袜子对数就是将所有颜色的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;
}

 

posted @ 2025-06-22 11:06  CRt0729  阅读(38)  评论(0)    收藏  举报