Bzoj2038小Z的袜子

Bzoj 2038 小Z的袜子

题目描述

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……
具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。
你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

输入

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

输出

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


solution

莫队入门模版题。
学了莫队算法以后想了很久为什么这个暴力能这么优美。
首先考虑离线操作
然后推公式。。。
根据题意得

ans=colori=1C2cnt[i]C2rl+1 a n s = ∑ i = 1 c o l o r C c n t [ i ] 2 C r − l + 1 2

cnt表示第i种颜色在 [l,r] [ l , r ] 的区间中出现了几次。
化简:

ans=colori=1cnt[i](cnt[i]1)2(rl+1)(rl)2 a n s = ∑ i = 1 c o l o r c n t [ i ] ∗ ( c n t [ i ] − 1 ) 2 ( r − l + 1 ) ∗ ( r − l ) 2

=colori=1cnt[i]2cnt[i](rl+1)(rl) = ∑ i = 1 c o l o r c n t [ i ] 2 − c n t [ i ] ( r − l + 1 ) ∗ ( r − l )

=colori=1cnt[i]2colori=1cnt[i](rl+1)(rl) = ∑ i = 1 c o l o r c n t [ i ] 2 − ∑ i = 1 c o l o r c n t [ i ] ( r − l + 1 ) ∗ ( r − l )

=colori=1cnt[i]2(rl+1)(rl+1)(rl) = ∑ i = 1 c o l o r c n t [ i ] 2 − ( r − l + 1 ) ( r − l + 1 ) ∗ ( r − l )

问题就转换成了维护一个区间内 cnt[i] c n t [ i ] 的平方和,现在假设我们已经知道了区间 [l,r] [ l , r ] 的答案,那么我们就可以在 O(1) O ( 1 ) 的时间求出 [l+1,r],[l1,r],[l,r1][l,r+1] [ l + 1 , r ] , [ l − 1 , r ] , [ l , r − 1 ] 和 [ l , r + 1 ] 的答案。
我们先考虑求 [l1,r] [ l − 1 , r ] [l,r+1], [ l , r + 1 ] , 因为是新增加一个数,可以放在一起讨论。
先令 ret=colori=1cnt[i]2 r e t = ∑ i = 1 c o l o r c n t [ i ] 2 ,当加进来一个数的时候 ret=retcnt[i]2+(cnt[i]+1])2 r e t = r e t − c n t [ i ] 2 + ( c n t [ i ] + 1 ] ) 2
化简变成 ret+=2cnt[i]+1 r e t + = 2 ∗ c n t [ i ] + 1
同理对于 [l+1,r] [ l + 1 , r ] [l,r1] [ l , r − 1 ] ,ret就变成了 ret2cnt[i]+1 r e t − 2 ∗ c n t [ i ] + 1 .
接下来就是莫队的是实现,什么样的转移方式比较快?
我们考虑对每个查询的左端点分块,块内按照右端点排序。
这样在每块中间要 O(n) O ( n ) 跑一边右端点, O(n) O ( n ) 跑一边左端点,因为一共有 n n 块,所以时间复杂度为 O(nn) O ( n n ) ..

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define maxn 100010
typedef long long LL;
inline int read(){
    int ret=0,ff=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') ff=-ff;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        ret=ret*10+ch-'0';
        ch=getchar();
    }
    return ret*ff;
}
int pos[maxn],w[maxn],n,m;
LL cnt[maxn],ans=-1;//每次算的时候会多加上一个1,所以提前减去
struct X{
    int l,r,id;
    LL x,y;
}q[maxn];
struct cmp1{
    bool operator()(const X& t1,const X& t2){
        if(pos[t1.l]==pos[t2.l]) return t1.r<t2.r;
        return t1.l<t2.l;
    }
};
struct cmp2{
    bool operator()(const X& t1,const X& t2){
        return t1.id<t2.id;
    }
};
LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
void update(int x,int f){
    ans+=2ll*cnt[w[x]]*f+1;
    cnt[w[x]]+=f;
}
void mo(){
    int l=0,r=0;
    for(int i=1;i<=m;++i){
        while(r<q[i].r) update(++r,1);
        while(r>q[i].r) update(r--,-1);
        while(l>q[i].l) update(--l,1);
        while(l<q[i].l) update(l++,-1);
        if(ans==r-l+1){q[i].x=0,q[i].y=1;continue;}
        q[i].x=ans-(r-l+1);
        q[i].y=1ll*(r-l+1)*(r-l);
        LL k=gcd(q[i].x,q[i].y);
        q[i].x/=k,q[i].y/=k;
    }
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;++i) w[i]=read();
    int blk=sqrt(n);
    for(int i=1;i<=n;++i) pos[i]=(i-1)/blk+1;
    for(int i=1;i<=m;++i) q[i].l=read(),q[i].r=read(),q[i].id=i;
    sort(q+1,q+1+m,cmp1());
    mo();
    sort(q+1,q+1+m,cmp2());
    for(int i=1;i<=m;++i) printf("%lld/%lld\n",q[i].x,q[i].y);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值