您的位置:首页 > 其它

【BZOJ】1878 HH的项链

2016-01-23 23:39 260 查看

Overview

求区间不同数的个数。

N≤50000N\leq 50000,M≤200000M\leq 200000

Analysis

1. 莫队算法

多个区间询问,有在线和离线的方法。先考虑离线吧。

按照块排序,使用莫队算法可以轻松解决

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

代码:

#include <cstdio>
#include <cmath>
#include <cctype>
#include <algorithm>
using namespace std;

const int N=65536;
const int M=200010;
const int D=1000001;

int n,a
;
int m,unit;
struct Q
{
int l,r,id;
friend inline int operator < (Q qa,Q qb)
{
return qa.l/unit!=qb.l/unit?qa.l/unit<qb.l/unit:qa.r<qb.r;
}
}q[M];

inline int Read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}

int vis[D],cnt;
int ans[M];

int main(void)
{
n=Read();
for (int i=1;i<=n;i++) a[i]=Read();
m=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);

int l=1,r=0;
for (int i=1;i<=m;i++)
{
for (;l>q[i].l;l--)
{
vis[a[l-1]]++;
if (vis[a[l-1]]==1) cnt++;
}
for (;l<q[i].l;l++)
{
vis[a[l]]--;
if (!vis[a[l]]) cnt--;
}
for (;r<q[i].r;r++)
{
vis[a[r+1]]++;
if (vis[a[r+1]]==1) cnt++;
}
for (;r>q[i].r;r--)
{
vis[a[r]]--;
if (!vis[a[r]]) cnt--;
}
ans[q[i].id]=cnt;
}

for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);

return 0;
}


2. 另一个离线算法

其实还可以想一想其他的离线算法。

按照左端点排序行不行?尝试一下吧。

排完序后,固定左端点l=1l=1,可以在O(n)O(n)求出此时[1,r][1,r]的不同数的个数f[i]f[i]。

接下来,我们考虑ll变成l+1l+1对∀r∈[l+1,n]\forall r\in[l+1,n]的影响。

①若[l+1,r][l+1,r]中不存在ala_l,那么f[r]−−f[r]--;

②若[l+1,r][l+1,r]中存在ala_l,那么f[r]f[r]不变。

我们只要找到下一个与ala_l相同的坐标nextlnext_l,那么将区间[l+1,nextl−1][l+1,next_l-1]中的所有f[i]f[i]减11即可。

区间更改,单点求值,考虑使用区间数据结构。

树状数组就可以了,应该会跑得很快的。

这个算法不知道网上有没有,自己想出来的233333。

时间复杂度:O(nlogn)O(n\log n)

代码:实测916ms916ms

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;

const int N=65536;
const int M=200010;
const int D=1000001;

int n;
int a
;

int tag[D];
int nxt
;

int m;
struct Ques
{
int l,r,id;
friend inline int operator < (Ques qa,Ques qb)
{
return qa.l<qb.l;
}
}q[M];
int ans[M];

inline int read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}

int cnt
;
struct TreeArray
{
int t
;
inline int lowbit(int i)
{
return i&-i;
}
inline void ins(int i,int add)
{
for (;i<=n;i+=lowbit(i)) t[i]+=add;
}
inline int query(int i)
{
int cnt=0;
for (;i;i-=lowbit(i)) cnt+=t[i];
return cnt;
}
}tr;

int main(void)
{
n=read();
for (int i=1;i<=n;i++) a[i]=read();

for (int i=1;i<=n;i++) tag[a[i]]=n+1;
for (int i=n;i>=1;i--) nxt[i]=tag[a[i]],tag[a[i]]=i;

m=read();
for (int i=1;i<=m;i++) q[i].id=i,q[i].l=read(),q[i].r=read();
sort(q+1,q+m+1);

for (int i=1;i<=n;i++) tag[a[i]]=0;
for (int i=1;i<=n;i++)
{
cnt[i]=cnt[i-1];
if (!tag[a[i]]) cnt[i]++; tag[a[i]]=1;
}

int now=1;
for (int i=1;i<=m;i++)
{
for (;now<q[i].l;now++) tr.ins(now+1,-1),tr.ins(nxt[now],1);
ans[q[i].id]=tr.query(q[i].r)+cnt[q[i].r];
}

for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);

return 0;
}


3. 可持久化数据结构的在线解法

不要从离线算法的排序入手,考虑从问题的解决入手。

区间[l,r][l,r]的不同数个数,就是每个在区间中最后一次出现的数的个数,也就是在[l,r][l,r]中满足next[i]>rnext[i]>r的个数,即[1,r][1,r]中满足next[i]>rnext[i]>r的个数减去[1,l−1][1,l-1]中满足next[i]>rnext[i]>r的个数。

以权值为点,坐标为链建立可持久化线段树在线解决问题!!!

代码:实测1956ms1956ms

#include <cstdio>
#include <cctype>

const int N=65536;
const int S=2097152;
const int D=1048576;

int n;
int a
;

inline int Read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}

int pre
,tmp[D];
struct Sgt
{
struct Node
{
int lc,rc;
int l,r;
int cnt;
}tr[S];
int tot;
int Build(int l,int r)
{
int now=++tot;
tr[now].l=l;
tr[now].r=r;
if (l!=r)
{
int mid=l+r>>1;
tr[now].lc=Build(l,mid);
tr[now].rc=Build(mid+1,r);
}
return now;
}
int Ins(int vtx,int w)
{
int now=++tot;
tr[now].l=tr[vtx].l;
tr[now].r=tr[vtx].r;
tr[now].cnt=tr[vtx].cnt+1;
if (tr[now].l!=tr[now].r)
{
int mid=tr[now].l+tr[now].r>>1;
if (w<=mid)
{
tr[now].lc=Ins(tr[vtx].lc,w);
tr[now].rc=tr[vtx].rc;
}
else
{
tr[now].lc=tr[vtx].lc;
tr[now].rc=Ins(tr[vtx].rc,w);
}
}
return now;
}
int Query(int vtx,int w)
{
if (tr[vtx].l==tr[vtx].r) return tr[vtx].cnt;
int mid=tr[vtx].l+tr[vtx].r>>1;
return w<=mid?Query(tr[vtx].lc,w):tr[tr[vtx].lc].cnt+Query(tr[vtx].rc,w);
}
}sgt;
int root
;

void Init(void)
{
n=Read();
for (int i=1;i<=n;i++) a[i]=Read();

for (int i=1;i<=n;i++)
{
pre[i]=tmp[a[i]];
tmp[a[i]]=i;
}

root[0]=sgt.Build(0,n);
for (int i=1;i<=n;i++)
root[i]=sgt.Ins(root[i-1],pre[i]);
}

int m;
int x,y;

void Work(void)
{
m=Read();
for (int i=1;i<=m;i++)
{
x=Read(),y=Read();
printf("%d\n",sgt.Query(root[y],x-1)-(x-1));
}
}

int main(void)
{
Init();
Work();
return 0;
}


跑得更慢了啊……

继续继续,一定还可以优化!

4. 改进算法三——又一个离线算法

求在[l,r][l,r]中满足next[i]>rnext[i]>r的个数,区间减法不仅可以用[1,r]−[1,l−1][1,r]-[1,l-1],还可以用[l,n]−[r+1,n][l,n]-[r+1,n],又注意到[r+1,n][r+1,n]的个数就是(n−r)(n-r)个,那么只用求出[l,n][l,n]中next[i]>rnext[i]>r的个数即可。

普遍的想法是把询问排序,然后通过ll的移动和权值线段树或者树状数组的更新完成询问。

先贴两个代码:

代码1:线段树(1164ms1164ms)

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;

const int N=65536;
const int S=2097152;
const int D=1048576;
const int M=S;

int n,m;
int a
;
struct Q
{
int l,r;
int id;
friend inline int operator < (Q qa,Q qb)
{
return qa.r<qb.r;
}
}q[M];
int pre
,tmp[D];

inline int Read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}

void Init(void)
{
n=Read();
for (int i=1;i<=n;i++) a[i]=Read();

for (int i=1;i<=n;i++)
{
pre[i]=tmp[a[i]];
tmp[a[i]]=i;
}

m=Read();
for (int i=1;i<=m;i++)
{
q[i].l=Read();
q[i].r=Read();
q[i].id=i;
}
sort(q+1,q+m+1);
}

struct Sgt
{
struct Node
{
int l,r;
int cnt;
}tr[S];
void Build(int now,int l,int r)
{
tr[now].l=l;
tr[now].r=r;
if (l!=r)
{
int mid=l+r>>1;
Build(now<<1,l,mid);
Build(now<<1|1,mid+1,r);
}
}
void Ins(int now,int w)
{
tr[now].cnt++;
if (tr[now].l==tr[now].r) return;
int mid=tr[now].l+tr[now].r>>1;
if (w<=mid) Ins(now<<1,w); else Ins(now<<1|1,w);
}
int Query(int now,int w)
{
if (tr[now].l==tr[now].r) return tr[now].cnt;
int mid=tr[now].l+tr[now].r>>1;
return w<=mid?Query(now<<1,w):tr[now<<1].cnt+Query(now<<1|1,w);
}
}sgt;
int now;
int ans[M];

void Work(void)
{
sgt.Build(1,0,n-1);
for (int i=1;i<=n;i++)
{
sgt.Ins(1,pre[i]);
for (;now<m&&q[now+1].r==i;now++)
ans[q[now+1].id]=sgt.Query(1,q[now+1].l-1)-(q[now+1].l-1);
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

int main(void)
{
Init();
Work();
return 0;
}



代码2:树状数组(实测876ms876ms)

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;

const int N=65536;
const int S=2097152;
const int D=1048576;
const int M=S;

int n,m;
int a
;
struct Q
{
int l,r;
int id;
friend inline int operator < (Q qa,Q qb)
{
return qa.r<qb.r;
}
}q[M];
int pre
,tmp[D];

inline int Read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}

void Init(void)
{
n=Read();
for (int i=1;i<=n;i++) a[i]=Read();

for (int i=1;i<=n;i++)
{
pre[i]=tmp[a[i]];
tmp[a[i]]=i;
}

m=Read();
for (int i=1;i<=m;i++)
{
q[i].l=Read();
q[i].r=Read();
q[i].id=i;
}
sort(q+1,q+m+1);
}

struct TreeArray
{
int tr
;
inline int lowbit(int i)
{
return i&-i;
}
void Ins(int i,int add)
{
for (i++;i<=n;i+=lowbit(i)) tr[i]+=add;
}
int Query(int i)
{
int sum=0;
for (i++;i;i-=lowbit(i)) sum+=tr[i];
return sum;
}
}tr;
int nowq;
int ans[M];

void Work(void)
{
for (int i=1;i<=n;i++)
{
tr.Ins(pre[i],1);
for (;nowq<m&&q[nowq+1].r==i;nowq++)
ans[q[nowq+1].id]=tr.Query(q[nowq+1].l-1)-(q[nowq+1].l-1);
}
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

int main(void)
{
Init();
Work();
return 0;
}


然而,我们可以继续优化!

优化的方法就是想办法把O(mlogm)O(m\log m)的排序给弄快一些。

排序已经做到最快了,我们能不能不排序

当然可以,像保存图一样开个链表就行了……

最后一个代码:实测832ms832ms

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;

const int N=65536;
const int M=200010;
const int D=1000001;

int n;
int a
;

int tag[D];
int nxt
;

int m;
struct G
{
int r,nxt;
}mp[M];
int tt,hd
;
int ans[M];

inline int read(void)
{
int x=0; char c=getchar();
for (;!isdigit(c);c=getchar());
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x;
}

inline int ins(int l,int r)
{
mp[++tt].r=r;
mp[tt].nxt=hd[l];
hd[l]=tt;
}

int cnt
;
struct TreeArray
{
int t
;
inline int lowbit(int i)
{
return i&-i;
}
inline void ins(int i,int add)
{
for (;i<=n;i+=lowbit(i)) t[i]+=add;
}
inline int query(int i)
{
int cnt=0;
for (;i;i-=lowbit(i)) cnt+=t[i];
return cnt;
}
}tr;

int main(void)
{
n=read();
for (int i=1;i<=n;i++) a[i]=read();

for (int i=1;i<=n;i++) tag[a[i]]=n+1;
for (int i=n;i>=1;i--) nxt[i]=tag[a[i]],tag[a[i]]=i;

int l,r; m=read();
for (int i=1;i<=m;i++) l=read(),r=read(),ins(l,r);

for (int i=n;i>=1;i--)
{
tr.ins(nxt[i]-1,1);
for (int k=hd[i];k;k=mp[k].nxt)
ans[k]=((n-i+1)-tr.query(mp[k].r-1))-(n-mp[k].r);
}

for (int i=1;i<=m;i++)
printf("%d\n",ans[i]);

return 0;
}


Sumarize

①区间问题可以从离线、在线的角度入手,也可以从问题的求解入手。

②区间问题离线的三种处理方法,不一定要排序,可以使用图来解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: