【BZOJ】1878 HH的项链

来源:互联网 发布:火影忍者ol精炼数据 编辑:程序博客网 时间:2024/05/16 04:53

Overview

求区间不同数的个数。
N50000M200000


Analysis

1. 莫队算法

多个区间询问,有在线和离线的方法。先考虑离线吧。
按照块排序,使用莫队算法可以轻松解决

时间复杂度:O(nn)

代码:

#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[N];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=1,可以在O(n)求出此时[1,r]的不同数的个数f[i]

接下来,我们考虑l变成l+1r[l+1,n]的影响。
①若[l+1,r]中不存在al,那么f[r]
②若[l+1,r]中存在al,那么f[r]不变。
我们只要找到下一个与al相同的坐标nextl,那么将区间[l+1,nextl1]中的所有f[i]1即可。

区间更改,单点求值,考虑使用区间数据结构。
树状数组就可以了,应该会跑得很快的。

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

时间复杂度:O(nlogn)

代码:实测916ms

#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[N];int tag[D];int nxt[N];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[N];struct TreeArray{    int t[N];    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]中满足next[i]>r的个数,即[1,r]中满足next[i]>r的个数减去[1,l1]中满足next[i]>r的个数。

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

代码:实测1956ms

#include <cstdio>#include <cctype>const int N=65536;const int S=2097152;const int D=1048576;int n;int a[N];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[N],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[N];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]中满足next[i]>r的个数,区间减法不仅可以用[1,r][1,l1],还可以用[l,n][r+1,n],又注意到[r+1,n]的个数就是(nr)个,那么只用求出[l,n]next[i]>r的个数即可。

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

先贴两个代码:

代码1:线段树(1164ms

#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[N];struct Q{    int l,r;    int id;    friend inline int operator < (Q qa,Q qb)    {        return qa.r<qb.r;    }}q[M];int pre[N],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:树状数组(实测876ms

#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[N];struct Q{    int l,r;    int id;    friend inline int operator < (Q qa,Q qb)    {        return qa.r<qb.r;    }}q[M];int pre[N],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[N];    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)的排序给弄快一些。
排序已经做到最快了,我们能不能不排序

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

最后一个代码:实测832ms

#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[N];int tag[D];int nxt[N];int m;struct G{    int r,nxt;}mp[M];int tt,hd[N];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[N];struct TreeArray{    int t[N];    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

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

②区间问题离线的三种处理方法,不一定要排序,可以使用图来解决。


0 0