【BZOJ】3339 RMQ Problems - Ⅰ - 题解

来源:互联网 发布:淘宝汽车冬季坐垫 编辑:程序博客网 时间:2024/06/15 15:37

【题意】
有一个长度为n的数组a1,a2,,anm次询问,每次询问一个区间[l,r]内最小没有出现过的自然数mex(al,al+1,...,ar)
【数据范围】
n,q200000
0ai200000aiZ
0<lrn

【分析1】30%做法:暴力
空间复杂度:O(n2)
时间复杂度:O(n2)

【分析2】树套树?

解决多个询问,考虑在线算法和离线算法。
先是在线算法,首先http://tieba.baidu.com/p/2813942502的算法应该错。
然后树套树应该是可以的。
但是蒟蒻并不会,所以以后应该会补充……

时间复杂度:O(nlog2n)
空间复杂度:unknown

【分析3】60%做法:莫队算法+线段树/树状数组

考虑离线算法。区间的离线算法,想到了莫队。
v[i]表示区间[l,r]中存在v[i]的个数,然后如果v[i]>0,则v[i]的权值赋为1
莫队移动的过程中,维护v数组,然后利用树状数组查找最小的没有出现过的数。
查找方法是从高位枚举到低位,检查权值是否满了,如果满了就用inc(cnt,2i)

时间复杂度:O(nnlogn)
空间复杂度:O(n)

代码:
(PS:时限是1s,然而我剩下4个点最多只有2s……)

#include <cstdio>#include <cctype>#include <cmath>#include <algorithm>using namespace std;const int N=262144;//131072*2const int MAX_BIT=20;int n,m;int a[N];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[N];int ans[N];int v[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(void)    {        int now=0;        for (int i=MAX_BIT;i>=0;i--)            if (now+(1<<i)<N&&t[now+(1<<i)]==1<<i) now+=1<<i;        return now;    }}tr;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 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);    int l=1,r=0;    for (int i=1;i<=m;i++)    {        for (;l<q[i].l;l++)        {            v[a[l]]--;            if (!v[a[l]]) tr.ins(a[l]+1,-1);        }        for (;l>q[i].l;l--)        {            v[a[l-1]]++;            if (v[a[l-1]]==1) tr.ins(a[l-1]+1,1);        }        for (;r<q[i].r;r++)        {            v[a[r+1]]++;            if (v[a[r+1]]==1) tr.ins(a[r+1]+1,1);        }        for (;r>q[i].r;r--)        {            v[a[r]]--;            if (!v[a[r]]) tr.ins(a[r]+1,-1);        }        ans[q[i].id]=tr.query();    }    for (int i=1;i<=m;i++)        printf("%d\n",ans[i]);    return 0;}

【分析4】100%做法:固定左端点,线段树维护每个点的sg

几个参考:
http://hzwer.com/3032.html
http://tieba.baidu.com/p/2813942502

这里只提实现线段树的方法有两种:①标久化标记 ②标记下传
方法①会快一些,在查询单点时只用取根到当前点路径的最小值。

时间复杂度:O(nlogn)
空间复杂度:O(nlogn)

代码:

/**************************************************************    Problem: 1286    User: YPL    Language: C++    Result: Accepted    Time:236 ms    Memory:54224 kb****************************************************************/#include <cstdio>#include <cstring>#include <cctype>#include <algorithm>using namespace std;const int N=200010;const int Q=200010;const int A=200010;const int S=4000000;int n,m;int a[N];struct Ques{    int l,r,id;    friend inline int operator < (Ques qa,Ques qb)    {        return qa.l<qb.l;    }}q[Q];int ans[Q];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;}int v[A];int nxt[N];int firSG[N],now;void Init(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;    sort(q+1,q+m+1);    fill(v,v+n+1,n+1);    for (int i=n;i>=1;i--) nxt[i]=v[a[i]],v[a[i]]=i;    memset(v,0,sizeof v);    for (int i=1;i<=n;i++) { for (v[a[i]]=1;v[now];now++); firSG[i]=now; }}struct SegmentTree{    struct TreeNode    {        int l,r;        int mex;    }tr[S];    void build(int now,int l,int r)    {        tr[now].l=l;        tr[now].r=r;        tr[now].mex=A;        if (l!=r)        {            int mid=l+r>>1;            build(now<<1,l,mid);            build(now<<1|1,mid+1,r);        }        else tr[now].mex=firSG[l];    }    void ins(int now,int l,int r,int w)    {        if (l<=tr[now].l&&tr[now].r<=r) {tr[now].mex=min(tr[now].mex,w);return;}        int mid=tr[now].l+tr[now].r>>1;        if (l<=mid) ins(now<<1,l,r,w);        if (mid<r) ins(now<<1|1,l,r,w);    }    int query(int now,int loc)    {        if (tr[now].l==tr[now].r) return tr[now].mex;        return min(tr[now].mex,query(now<<1|loc>tr[now].l+tr[now].r>>1,loc));    }}tr;int cir=1;void Work(void){    tr.build(1,1,n);    for (int i=1;i<=m;i++)    {        for (;cir<q[i].l;cir++)            tr.ins(1,cir+1,nxt[cir]-1,a[cir]);        ans[q[i].id]=tr.query(1,q[i].r);    }    for (int i=1;i<=m;i++) printf("%d\n",ans[i]);}int main(void){    Init();    Work();    return 0;}

【小结】

  1. 多个询问的处理维度:在线算法,离线算法

  2. 区间[l,r]离线处理的常见排序方法:
    ①按照l排序
    ②按照r排序
    ③分块排序
    通常①②的方法效率会比③高。

  3. 区间答案的处理
    ①区间加法
    ②区间减法

  4. 使用方法①②,只要能搞掂一个端点的改变对所有函数值变化的一般规律即可。通常可以快速变化的函数是具有单调性的,如本题的mex函数。

  5. mex函数的两个经验:
    ①具有单调性
    ②求nxt数组

1 0
原创粉丝点击