您的位置:首页 > 其它

【BZOJ】2038 小Z的袜子

2016-01-23 11:56 337 查看

Problem

【题意】在长度为nn的序列aa中,有mm个询问,每次求区间[l,r][l,r]中选择两个点(ai,aj)(a_i,a_j),满足ai≠aja_i\neq a_j。

【数据范围】

2≤N,M≤500002\leq N,M\leq 50000

1≤L<R≤N1\leq L

1≤ai≤N1\leq a_i\leq N

Analysis

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

本身做这道题就是为了打一遍模板,然后复习一下复杂度分析,用来准备分析一下强制在线莫队的复杂度和UnitUnit的取值大小的推导。

然而模板不小心打错了,”//”写成了”<<”,不小心弄了一个上午……

下面开始分析复杂度。

先是把询问排序,我的写法:

第一关键字是ll所在的块,

第二关键字是rr的大小。

时间复杂度为O(mlogm)O(m\log m)。

然后是莫队算法。

①当ll在块内移动时,每次移动m−−√\sqrt m以内,最多移动nn次。

时间复杂度为:O(mn−√)O(m\sqrt n)

②当ll移动到块外时,总共最多能移动nn步。

时间复杂度为:O(n)O(n)

③当rr移动时,在ll在同一块内,最多移动nn步,而一共不超过n−√\sqrt n个块。

时间复杂度为:O(nn−√)O(n\sqrt n)

综上所述,总的时间复杂度为:

O(mlogm)+O(mn−√)+O(n)+O(nn−√)=O(nn−√)O(m\log m)+O(m\sqrt n)+O(n)+O(n\sqrt n)=O(n\sqrt n)。

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

Question

其实看了VFK糖果公园的题解,我还想到了一个问题:

排序的时候,第一关键字为ll所在的块,第二关键字为rr所在的块行不行?

其实是可以的,这里再来一次分析当练习。

分以下44类讨论:

①当ll在块内移动时,O(mn−√)O(m\sqrt n)

②当ll移动到块外时,O(n)O(n)

③当rr在块内移动时,O(mn−√)O(m\sqrt n)

④当rr移动到块外时,O(nn−√)O(n\sqrt n)

综上所述,时间复杂度为O(nn−√)O(n\sqrt n)。

Code

【代码1】

排序方式:

第一关键字:ll所在的块;

第二关键字:rr从小到大。

实测: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
;

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
;
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】

排序方式:

第一关键字:ll所在的块

第二关键字:rr所在的块

实测: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
;

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
;
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

首先是写莫队需要注意的一个地方:

在排序的时候,不要把”//”写成了”<<”。

然后是莫队算法的排序方式:

按块来分,最后一个关键字可以直接从小到大。

近而回顾离线算法的排序方式。本来是这样的:

①按照左端点排序 ②按照右端点排序 ③按照块排序

现在可以补充一点,就是说我们可以使用多关键字排序。

然后是莫队的复杂度分析的方法:

按照不同端点、块内外来分类讨论。

就这些了,希望不要再因为类似的错误导致一个上午的颓废。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: