【BZOJ 2038】小Z的袜子 (莫队算法)

传送门

BZOJ 2038 小Z的袜子

I think

    题意:给出长度为n的区间与若干形如[l,r]的区间,询问在[l,r]内两个不同位置取到两个相同的数的概率。
    算法:莫队
    思想:将组合数求概率的式子化简一下:

P()=coli=1C2f[i]C2rl+1=coli=1C2f[i](rl+1)(rl)2

(col 表示区间内颜色总数 f[i]表示区间内颜色出现次数)

    于是我们可以看伟大的黄学长题解

莫队算法
如果我们已知[l,r]的答案,能在O(1)时间得到[l+1,r]的答案以及[l,r-1]的答案,即可使用莫队算法。时间复杂度为O(n^1.5)。如果只能在logn的时间移动区间,则时间复杂度是O(n^1.5*log n)。
其实就是找一个数据结构支持插入、删除时维护当前答案。
这道题的话我们很容易用数组来实现,做到O(1)的从[l,r]转移到[l,r+1]与[l+1,r]。
那么莫队算法怎么做呢?以下都是在转移为O(1)的基础下讨论的时间复杂度。另外由于n与m同阶,就统一写n。
如果已知[l,r]的答案,要求[l’,r’]的答案,我们很容易通过|l – l’|+|r – r’|次转移内求得。
将n个数分成sqrt(n)块。
按区间排序,以左端点所在块内为第一关键字,右端点为第二关键字,进行排序,也就是以(pos [l],r)排序
然后按这个排序直接暴力,复杂度分析是这样的:
1、i与i+1在同一块内,r单调递增,所以r是O(n)的。由于有n^0.5块,所以这一部分时间复杂度是n^1.5。
2、i与i+1跨越一块,r最多变化n,由于有n^0.5块,所以这一部分时间复杂度是n^1.5
3、i与i+1在同一块内时l变化不超过n^0.5,跨越一块也不会超过n^0.5,忽略*2。由于有m次询问(和n同级),所以时间复杂度是n^1.5
于是就是O(n^1.5)了

最后最小公倍数约一下分子分母就可以输出答案了。

Code

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int sm = 50000+10;
LL Ans;
int N,M,Blk;
int S[sm],C[sm],pos[sm];
struct Que {
    int id,l,r;
    LL a,b;
}A[sm];
LL Gcd(LL a,LL b) { return b == 0?a:Gcd(b,a%b); } 
LL ind(int x) { return 1ll*x*x; }
void read(int &x) {
    char ch=getchar();x=0;
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void Out(LL a) {
    if(a>9) Out(a/10);
    putchar('0'+a%10);
}
bool cmpa(Que x,Que y) {
    return (pos[x.l]!=pos[y.l])?pos[x.l]<pos[y.l]:x.r<y.r;
}
bool cmpb(Que x,Que y) { return x.id<y.id; }
void Update(int p,int add) {
    Ans-=ind(S[C[p]]);
    S[C[p]]+=add;
    Ans+=ind(S[C[p]]);
}
void Solve() {
    int l=1,r=0;//注意l=1的细节
    LL k;
    for(int i=1;i<=M;++i) {
        for(;r<A[i].r;++r) Update(r+1,1);
        for(;r>A[i].r;--r) Update(r,-1);
        for(;l<A[i].l;++l) Update(l,-1);
        for(;l>A[i].l;--l) Update(l-1,1);
        if(A[i].l==A[i].r) {
            A[i].a=0,A[i].b=1;
            continue;   
        }   
        A[i].a=Ans-(A[i].r-A[i].l+1);
        A[i].b=1ll*(A[i].r-A[i].l+1)*(A[i].r-A[i].l);
        k=Gcd(A[i].a,A[i].b);
        A[i].a/=k,A[i].b/=k;
    }
}
int main() {
    read(N),read(M);
    Blk=sqrt(N);
    for(int i=1;i<=N;++i)
        read(C[i]),pos[i]=(i-1)/Blk+1;
    for(int i=1;i<=M;++i)
        read(A[i].l),read(A[i].r),A[i].id=i;
    sort(A+1,A+M+1,cmpa);
    Solve();
    sort(A+1,A+M+1,cmpb);
    for(int i=1;i<=M;++i) {
        Out(A[i].a),putchar('/');
        Out(A[i].b),putchar(10);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值