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

来源:互联网 发布:电脑usb端口上的电涌 编辑:程序博客网 时间:2024/06/02 18:55

题目大意:给出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 300using 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;}
0 0