【BZOJ】1878 HH的项链


Overview

求区间不同数的个数。
N50000 M200000


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+1 r[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
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值