BZOJ 2038 [2009国家集训队] 小Z的袜子 莫队算法

题目大意:给出n个元素的序列,m个区间,求
这里写图片描述
其中f(j)表示在区间内颜色j出现的次数。
n,m<=50000

如果对于每个区间暴力统计颜色个数然后计算,时间复杂度O(mnq),q为颜色种类数。
瞬间爆炸。考虑怎么优化

不难发现,如果已知[l,r]的答案,可以在O(1)的时间内求出[l,r+1]/[l+1,r]/[l-1,r]/[l,r-1]的答案并更新(颜色)信息,这就是(传说中的)莫队算法。

但是,即使这样每次转移的时间复杂度仍然高达O(nm),考虑离线处理。

把区间分块,按照第一关键字为左端点按照所在的块的编号、第二关键字为右端点大小排序,依次转移。
左端点:在 一个块中转移 和 块与块之间转移 的时间复杂度均为O(sqrt(n))
右端点:对于左端点都在一个块中的询问,排序后右端点从小到大,时间复杂度为O(n),共sqrt(n)个块,时间复杂度为O(n*sqrt(n)). 左端点跨越一个块的时候,右端点最坏情况从最后跑到最前面,时间复杂度O(n),共n个块,时间复杂度O(n*sqrt(n)).

总时间复杂度O(n*sqrt(n))

莫队算法可以解决能离线处理且不满足区间加法的区间询问

分块大法好。

#include <cstdio>
#include <cmath>
#include <algorithm>
#define N 50005
#define M 300
using namespace std;
typedef long long LL;
int n,m,p,L,R,ans,a[N],cnt[N],pos[N];
struct Segment{
    int l,r,ord,ansx,ansy;
    bool operator < (const Segment& rhs) const {return pos[l]<pos[rhs.l] || pos[l]==pos[rhs.l] && r<rhs.r; }
}b[N];
bool cmp(const Segment& x,const Segment& y){return x.ord<y.ord;}
int gcd(int x,int y){ return !y ? x : gcd(y,x%y); }
int main(){
    scanf("%d%d",&n,&m);
    p=floor(sqrt(n));
    for(int i=1;i<=n;i++)
        if(i%p) pos[i]=i/p;
        else pos[i]=i/p-1;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=m;i++) scanf("%d%d",&b[i].l,&b[i].r) , b[i].ord=i;
    sort(b+1,b+1+m);
    L=R=1; cnt[a[1]]++;
    for(int i=1;i<=m;i++){
        //upgrade
        if(L<b[i].l) for(;L<b[i].l;L++) ans -= --cnt[a[L]];
        else for(L--;L>=b[i].l;L--) ans += cnt[a[L]]++;
        if(R>b[i].r) for(;R>b[i].r;R--) ans -= --cnt[a[R]];
        else for(R++;R<=b[i].r;R++) ans += cnt[a[R]]++;
        L=b[i].l , R=b[i].r;
        b[i].ansx=ans , b[i].ansy=(LL)(b[i].r-b[i].l)*(b[i].r-b[i].l+1)/2;
    }
    sort(b+1,b+1+m,cmp);
    for(int i=1;i<=m;i++){
        if(!b[i].ansx) printf("0/1\n");
        else n=gcd(b[i].ansx,b[i].ansy) , printf("%d/%d\n",b[i].ansx/n,b[i].ansy/n);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值