【BZOJ】3339 RMQ Problems - Ⅰ - 题解

【题意】
有一个长度为 n 的数组a1,a2,,an m 次询问,每次询问一个区间[l,r]内最小没有出现过的自然数 mex(al,al+1,...,ar)
【数据范围】
n,q200000
0ai200000 aiZ
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*2
const 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 数组

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值