小z的袜子

传送门

题意

  给出n个数以及m个区间,求在每个区间内选出两个数,有多大的概率使选出的两个数相等。

solution

对于区间(l,r)的询问。

设其中颜色为x,y,z的袜子的个数为a,b,c...

那么答案即为 (a∗(a−1)/2+b∗(b−1)/2+c∗(c−1)/2....)/((R−L+1)∗(R−L)/2)

化简得: (a^2+b^2+c^2+...x^2-(a+b+c+d+.....))/((R-L+1)*(R-L))

即:(a^2+b^2+c^2+...x^2-(R-L+1))/((R-L+1)*(R-L))

我们需要解决的一个问题

求一个区间内每种颜色数目的平方和。

所以这道题目的关键是求一个区间内每种颜色数目的平方和。

但问题时怎么快速求解呢?

对于一般区间维护类问题一般想到用线段树。但是这题完全不知道线段树怎么做,所以只能用莫队算法了。

莫队算法是离线处理一类区间不修改查询类问题的算法。就是如果你知道了[L,R]的答案。你可以在O(1)的时间下得到[L,R−1]和[L,R+1]和[L−1,R]和[L+1,R]的答案的话。就可以使用莫队算法。

  对于莫队算法其实就是暴力。只是预先知道了所有的询问。可以合理的组织计算每个询问的顺序以此来降低复杂度。要知道我们算完[L,R]的答案后现在要算[L′,R′]的答案。由于可以在O(1)的时间下得到[L,R−1]和[L,R+1]和[L−1,R]和[L+1,R]的答案。所以计算[L′,R′]的答案花的时间为|L−L′|+|R−R′|。如果把询问[L,R]看做平面上的点a(L,R);询问[L′,R′]看做点b(L′,R′)的话。那么时间开销就为两点的曼哈顿距离。所以对于每个询问看做一个点。我们要按一定顺序计算每个值。那开销就为曼哈顿距离的和。要计算到每个点。那么路径至少是一棵树。所以问题就变成了求二维平面的最小曼哈顿距离生成树。

#include<bits/stdc++.h>
#include<algorithm>
#include <math.h>
using namespace std;
#define ll long long
typedef pair<ll ,ll>pa;
#define pre(i,x,n) for(int i=x;i<=n;i++)
#define rep(i,n,x) for(int i=n;i>=x;i--)
//priority_queue<ll ,vector<ll>,greater<ll> >q;
ll n,m,a[50010],ans=0,cnt[50010]={0},sum[50010][3];
int tem;
struct mo
{
    int l,r,id;
}q[50010];
int cmp(mo x,mo y)
{
    if(x.l/tem==y.l/tem)
      return x.r<y.r;
    return x.l/tem<y.l/tem;
}
int gcd(ll x,ll y)
{
     return x?gcd(y%x,x):y;
}
void add(int x)
{
     ans-=cnt[a[x]]*cnt[a[x]];
     cnt[a[x]]++;
     ans+=cnt[a[x]]*cnt[a[x]];
}
void del(int x)
{
     ans-=cnt[a[x]]*cnt[a[x]];
     cnt[a[x]]--;
     ans+=cnt[a[x]]*cnt[a[x]];
}
int main()
{
    scanf("%lld%lld",&n,&m);
    tem=sqrt(n);
    pre(i,1,n)scanf("%lld",&a[i]);
    pre(i,1,m)
    {
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    sort(q+1,q+1+m,cmp);
    int l=1,r=0;
    pre(i,1,m)
    {
        while(r<q[i].r)add(++r);
        while(l>q[i].l)add(--l);
        while(l<q[i].l)del(l++);
        while(r>q[i].r)del(r--);
        sum[q[i].id][0]=ans-r+l-1;
        sum[q[i].id][1]=1ll*(r-l+1)*(r-l);
//一定要先判断一下,防止出现gcd(0,0)的情况,让后面g=0,提交时出现RE(运行错误)
        if(q[i].l==q[i].r)
        {
            sum[q[i].id][0]=0;
            sum[q[i].id][1]=1;
            continue;
        }
        int g=gcd(sum[q[i].id][0],sum[q[i].id][1]);
        sum[q[i].id][0]/=g;
        sum[q[i].id][1]/=g;
    }
    pre(i,1,m){printf("%lld/%lld\n",sum[i][0],sum[i][1]);}
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2020/3/16

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值