Luogu 1494 - 小Z的袜子 - [莫队算法模板题]

题目链接:https://www.luogu.org/problemnew/show/P1494

题目描述

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……

具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。

你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

然而数据中有L=R的情况,请特判这种情况,输出0/1。

输入输出格式

输入格式:

输入文件第一行包含两个正整数N和M。N为袜子的数量,M为小Z所提的询问的数量。接下来一行包含N个正整数Ci,其中Ci表示第i只袜子的颜色,相同的颜色用相同的数字表示。再接下来M行,每行两个正整数L,R表示一个询问。

输出格式:

包含M行,对于每个询问在一行中输出分数A/B表示从该询问的区间[L,R]中随机抽出两只袜子颜色相同的概率。若该概率为0则输出0/1,否则输出的A/B必须为最简分数。(详见样例)

输入输出样例

输入样例#1:
6 4
1 2 3 3 3 2
2 6
1 3
3 5
1 6
输出样例#1:
2/5
0/1
1/1
4/15

说明

30%的数据中 N,M ≤ 5000;

60%的数据中 N,M ≤ 25000;

100%的数据中 N,M ≤ 50000,1 ≤ L < R ≤ N,Ci ≤ N。

题解:

直接上线段树的话,由于不满足加和性质

假设现在要计算区间 [ l , r ] 里每一种颜色的袜子的数量,此时我们已经分别维护好了左右两边区间 [ l , mid ] 和 [ mid+1 , r ] 中每一种颜色的袜子的数量,然后开始向上维护:

就会发现,依然要把每种颜色的袜子在左右区间的数量加起来才能完成对一个父节点的维护,我们并不能在O(1)的时间内完成对线段树单一节点的维护。

但此时,若已知一个区间 [ l , r ] 的情况,我们可以在O(1)的时间内确定的有 [ l , r+1 ] 和 [ l , r-1 ] 和 [ l-1 , r ] 和 [ l+1 , r ] 的情况,

假设我们现在已知 [ l , r ] 的情况,需要计算的是 [ l' , r' ] 的情况,易得所需时间复杂度为O( | l - l' | + | r - r' | ),

那么怎么样能够在较短的时间内以较少的代码量回答Query呢?

先对序列sqrt(N)分块,然后以询问的区间左端点所在的分块的序号为第一优先级,区间右端点的大小为第二优先级进行排序,然后按顺序计算,复杂度就会大大降低。

按上述顺序,从第i个区间到第i+1个区间有如下情况:

那么假设某个区间有X(X≥2)只不同袜子,那么任取两只袜子的可能数是 $C_X^2$ ,

假设区间内的袜子有1~K共K种颜色,假设第k个颜色的袜子数为num[k],那么取到同种颜色的两只袜子的可能数是 $C_{num\left[ 1 \right]}^2 + C_{num\left[ 2 \right]}^2 \cdots C_{num\left[ K \right]}^2$ ,

根据组合数公式可知 $C_n^2 = \frac{{n\left( {n - 1} \right)}}{2}$ ,

所以取到两只同色袜子的概率为 $\frac{{num\left[ 1 \right]\left( {num\left[ 1 \right] - 1} \right) + \cdots + num\left[ K \right]\left( {num\left[ K \right] - 1} \right)}}{{X\left( {X - 1} \right)}}$ ,

即 $\frac{{num\left[ 1 \right]{}^2 + \cdots + num\left[ K \right]^2 - X}}{{X\left( {X - 1} \right)}}$ 。

由于 $X = R - L + 1$ ,我们实际要查询的就是 $Q\left( {L,R} \right) = \sum\limits_{k = 1}^K {num\left[ k \right]^2 }$ ,

那么,

若区间转移时增加一只颜色为 $c$ 的袜子:

$Q\left( {L,R + 1} \right) = Q\left( {L - 1,R} \right) = Q\left( {L,R} \right) - num\left[ c \right]^2 + \left( {num\left[ c \right] + 1} \right)^2 = Q\left( {L,R} \right) + 2 \times num\left[ c \right] + 1$

若区间转移时减少一只颜色为 $c$ 的袜子:

$Q\left( {L,R - 1} \right) = Q\left( {L{\rm{ + }}1,R} \right) = Q\left( {L,R} \right) - num\left[ c \right]^2 + \left( {num\left[ c \right] - 1} \right)^2 = Q\left( {L,R} \right) - 2 \times num\left[ c \right] + 1$

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN=50000+10;
const int MAXM=50000+10;

class Interval
{
public:
    int block; //表示左端点所在块
    int id; //表示询问的原始顺序
    int l,r;
    bool operator <(const Interval &oth) const
    {
        return (this->block == oth.block) ? (this->r < oth.r) : (this->block < oth.block);
    }
}itv[MAXM];

int n,m;
int color[MAXN],num[MAXN];
LL up[MAXN],down[MAXN];

inline LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}

int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int len=sqrt(n);
        for(int i=1;i<=n;i++) scanf("%d",&color[i]);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&itv[i].l,&itv[i].r);
            itv[i].block=itv[i].l/len;
            itv[i].id=i;
            down[i]=(itv[i].r-itv[i].l)/2*(itv[i].r-itv[i].l+1);
        }
        sort(itv+1,itv+m+1);

        memset(num,0,sizeof(num));
        int pl=1,pr=0;
        LL Q=0;
        for(int i=1;i<=m;i++)
        {
            if(itv[i].l==itv[i].r)
            {
                down[itv[i].id]=1;
                up[itv[i].id]=0;
                continue;
            }

            down[itv[i].id]=(LL)(itv[i].r-itv[i].l+1)*(itv[i].r-itv[i].l);

            if(pr<itv[i].r)
            {
                for(int j=pr+1;j<=itv[i].r;j++)
                {
                    Q=Q+2*num[color[j]]+1;
                    num[color[j]]++;
                }
            }
            if(pr>itv[i].r)
            {
                for(int j=pr;j>itv[i].r;j--)
                {
                    Q=Q-2*num[color[j]]+1;
                    num[color[j]]--;
                }
            }
            if(pl>itv[i].l)
            {
                for(int j=pl-1;j>=itv[i].l;j--)
                {
                    Q=Q+2*num[color[j]]+1;
                    num[color[j]]++;
                }
            }
            if(pl<itv[i].l)
            {
                for(int j=pl;j<itv[i].l;j++)
                {
                    Q=Q-2*num[color[j]]+1;
                    num[color[j]]--;
                }
            }

            up[itv[i].id]=Q-(itv[i].r-itv[i].l+1);

            pl=itv[i].l;
            pr=itv[i].r;
        }

        for(int i=1;i<=m;i++)
        {
            LL g=gcd(up[i],down[i]);
            printf("%lld/%lld\n",up[i]/g,down[i]/g);
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/dilthey/p/9225878.html
今日推荐