【BZOJ】2038 小Z的袜子

来源:互联网 发布:python product 函数 编辑:程序博客网 时间:2024/06/11 23:41

Problem

【题意】在长度为n的序列a中,有m个询问,每次求区间[l,r]中选择两个点(ai,aj),满足aiaj

【数据范围】
2N,M50000
1L<RN
1aiN


Analysis

莫队算法怎么做?百度一下随便点开即可。

本身做这道题就是为了打一遍模板,然后复习一下复杂度分析,用来准备分析一下强制在线莫队的复杂度和Unit的取值大小的推导。
然而模板不小心打错了,”/”写成了”<”,不小心弄了一个上午……
下面开始分析复杂度。

先是把询问排序,我的写法:
第一关键字是l所在的块,
第二关键字是r的大小。
时间复杂度为O(mlogm)

然后是莫队算法。
①当l在块内移动时,每次移动m以内,最多移动n次。
时间复杂度为:O(mn)
②当l移动到块外时,总共最多能移动n步。
时间复杂度为:O(n)
③当r移动时,在l在同一块内,最多移动n步,而一共不超过n个块。
时间复杂度为:O(nn)

综上所述,总的时间复杂度为:
O(mlogm)+O(mn)+O(n)+O(nn)=O(nn)

回顾上述的分析方法,也就是按照块内外、不同的端点分类讨论而已。


Question

其实看了VFK糖果公园的题解,我还想到了一个问题:
排序的时候,第一关键字为l所在的块,第二关键字为r所在的块行不行?
其实是可以的,这里再来一次分析当练习。

分以下4类讨论:
①当l在块内移动时,O(mn)
②当l移动到块外时,O(n)
③当r在块内移动时,O(mn)
④当r移动到块外时,O(nn)
综上所述,时间复杂度为O(nn)


Code

【代码1】

排序方式:
第一关键字:l所在的块;
第二关键字:r从小到大。

实测:2080 ms

#include <cstdio>#include <cctype>#include <cmath>#include <algorithm>using namespace std;typedef long long Lint;const int N=65536;const int M=65536;int n;int a[N];int m;int unit;struct Ques{    int l,r,id;    friend inline int operator < (Ques qa,Ques qb)    {        return qa.l/unit!=qb.l/unit?qa.l/unit<qb.l/unit:qa.r<qb.r;    }}q[M];Lint ans[M][2];int cnt[N];int l=1,r=0;Lint res;inline int read(void){    int x=0,f=1; char c=getchar();    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;    for (;isdigit(c);c=getchar()) x=x*10+c-'0';    return x*f;}inline void add(int w,int k){    res-=(Lint)cnt[w]*(cnt[w]-1)>>1;    cnt[w]+=k;    res+=(Lint)cnt[w]*(cnt[w]-1)>>1;}inline Lint gcd(Lint i,Lint j){    for (Lint r;j;r=i%j,i=j,j=r); return i;}int main(void){    n=read(),m=read();    for (int i=1;i<=n;i++) a[i]=read();    for (int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;    unit=(int)sqrt(n);    sort(q+1,q+m+1);    Lint g;    for (int i=1;i<=m;i++)    {        for (;l<q[i].l;l++) add(a[l],-1);        for (;l>q[i].l;l--) add(a[l-1],1);        for (;r<q[i].r;r++) add(a[r+1],1);        for (;r>q[i].r;r--) add(a[r],-1);        ans[q[i].id][0]=res,ans[q[i].id][1]=(Lint)(q[i].r-q[i].l+1)*(q[i].r-q[i].l)>>1;        g=gcd(ans[q[i].id][0],ans[q[i].id][1]);        for (int k=0;k<=1;k++) ans[q[i].id][k]/=g;    }    for (int i=1;i<=m;i++)        printf("%lld/%lld\n",ans[i][0],ans[i][1]);    return 0;}

【代码2】

排序方式:
第一关键字:l所在的块
第二关键字:r所在的块

实测:2396 ms

#include <cstdio>#include <cctype>#include <cmath>#include <algorithm>using namespace std;typedef long long Lint;const int N=65536;const int M=65536;int n;int a[N];int m;int unit;struct Ques{    int l,r,id;    friend inline int operator < (Ques qa,Ques qb)    {        return qa.l/unit!=qb.l/unit?qa.l/unit<qb.l/unit:qa.r/unit<qb.r/unit;    }}q[M];Lint ans[M][2];int cnt[N];int l=1,r=0;Lint res;inline int read(void){    int x=0,f=1; char c=getchar();    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;    for (;isdigit(c);c=getchar()) x=x*10+c-'0';    return x*f;}inline void add(int w,int k){    res-=(Lint)cnt[w]*(cnt[w]-1)>>1;    cnt[w]+=k;    res+=(Lint)cnt[w]*(cnt[w]-1)>>1;}inline Lint gcd(Lint i,Lint j){    for (Lint r;j;r=i%j,i=j,j=r); return i;}int main(void){    n=read(),m=read();    for (int i=1;i<=n;i++) a[i]=read();    for (int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;    unit=(int)sqrt(n);    sort(q+1,q+m+1);    Lint g;    for (int i=1;i<=m;i++)    {        for (;l<q[i].l;l++) add(a[l],-1);        for (;l>q[i].l;l--) add(a[l-1],1);        for (;r<q[i].r;r++) add(a[r+1],1);        for (;r>q[i].r;r--) add(a[r],-1);        ans[q[i].id][0]=res,ans[q[i].id][1]=(Lint)(q[i].r-q[i].l+1)*(q[i].r-q[i].l)>>1;        g=gcd(ans[q[i].id][0],ans[q[i].id][1]);        for (int k=0;k<=1;k++) ans[q[i].id][k]/=g;    }    for (int i=1;i<=m;i++)        printf("%lld/%lld\n",ans[i][0],ans[i][1]);    return 0;}

Sumarize

首先是写莫队需要注意的一个地方:
在排序的时候,不要把”/”写成了”<”。

然后是莫队算法的排序方式:
按块来分,最后一个关键字可以直接从小到大。
近而回顾离线算法的排序方式。本来是这样的:
①按照左端点排序 ②按照右端点排序 ③按照块排序
现在可以补充一点,就是说我们可以使用多关键字排序。

然后是莫队的复杂度分析的方法:
按照不同端点、块内外来分类讨论。

就这些了,希望不要再因为类似的错误导致一个上午的颓废。


0 0
原创粉丝点击