CodeForces - 1000F One Occurrence(莫队+分块 | 线段树 | 主席树)

F. One Occurrence

time limit per test

3 seconds

memory limit per test

768 megabytes

input

standard input

output

standard output

You are given an array aa consisting of nn integers, and qq queries to it. ii-th query is denoted by two integers lili and riri. For each query, you have to find any integer that occurs exactly once in the subarray of aa from index lili to index riri (a subarray is a contiguous subsegment of an array). For example, if a=[1,1,2,3,2,4]a=[1,1,2,3,2,4], then for query (li=2,ri=6)(li=2,ri=6) the subarray we are interested in is [1,2,3,2,4][1,2,3,2,4], and possible answers are 11, 33 and 44; for query (li=1,ri=2)(li=1,ri=2) the subarray we are interested in is [1,1][1,1], and there is no such element that occurs exactly once.

Can you answer all of the queries?

Input

The first line contains one integer nn (1≤n≤5⋅1051≤n≤5⋅105).

The second line contains nn integers a1,a2,…,ana1,a2,…,an (1≤ai≤5⋅1051≤ai≤5⋅105).

The third line contains one integer qq (1≤q≤5⋅1051≤q≤5⋅105).

Then qq lines follow, ii-th line containing two integers lili and riri representing ii-th query (1≤li≤ri≤n1≤li≤ri≤n).

Output

Answer the queries as follows:

If there is no integer such that it occurs in the subarray from index lili to index riri exactly once, print 00. Otherwise print any such integer.

Example

input

Copy

6
1 1 2 3 2 4
2
2 6
1 2

output

Copy

4
0

 

题意:区间查询只出现了一次的数字,输出一个即可。

解题思路:三种算法。

1.莫队+优化 (2510ms)

可以O(1)的转移,用一个数组记录数字出现的次数即可。关键在于统计答案。这里用set维护只出现了一次的数字貌似会超时(常数大)。所以这里直接用分块处理。块内维护出现了一次的数字的个数。每次统计答案的时候我们遍历每一个块,一旦块内答案>0,那么我们就暴力查找这一个块内,出现了一次的数字。最后的复杂度还是N*根号N级别的。按照出题者原话,直接用莫队还是会超时,因为N有50w。你这里用到了一个莫队的小优化才能过,就是在对查询的时候,我们对右边界按照爬坡那样排序,即一高一低,具体看代码。

2.线段树维护前一次出现的位置的最小值(873ms)

对于每一个数字我们可以预处理出他前一次出现的位置。然后我们对所有查询按照右端点排序,然后暴力插入。然后区间查询前一次出现的最小值,如果最小值<l,那么证明有答案,否则没答案。这里要用pair,分别记录前一次出现的位置,和指向这一个位置的位置,方便统计答案。具体细节看代码。

3.主席树(1809ms)

跟线段树一样,但是不用排序了,因为主席树可以在线做。这里要注意每次更新都要重新计算根节点。

 

 

莫队代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <memory.h>
#include <bitset>
#include <map>
#include <deque>
#include <math.h>
#include <stdio.h>
using namespace std;
typedef long long int ll;
const int MAXN = 500005;

int N,Q;
int a[MAXN];

int block[MAXN];
int blocksize;
int blocknum;
int bl[MAXN],br[MAXN];//每一块的左右边界
int tot=0;

struct query{
    int l,r,id;
}q[MAXN];
bool cmp(const query &a,const query &b){
    if(block[a.l]!=block[b.l])
        return a.l<b.l;
    if(block[a.l]&1)//莫队优化
        return a.r<b.r;
    return a.r>b.r;
}

int bonenum[MAXN];
int num[MAXN];

void add(int x){
    num[x]++;
    if(num[x]==1)
        bonenum[block[x]]++,tot++;
    else if(num[x]==2)
        bonenum[block[x]]--,tot--;
}

void remove(int x){
    num[x]--;
    if(num[x]==1)
        bonenum[block[x]]++,tot++;
    else if(num[x]==0)
        bonenum[block[x]]--,tot--;
}

int getans(){
    if(tot==0)//剪枝
        return 0;
    for(int b=1;b<=blocknum;b++){
        if(bonenum[b]>0)//暴力找
        {
            for(int i=bl[b];i<=br[b];i++){
                if(num[i]==1){
                    return i;
                }
            }
        }
    }
    return 0;
}


int ans[MAXN];
void solve(){
    int l=1,r=1;
    add(a[1]);
    for(int i=0;i<Q;i++){
        while(q[i].l>l)
            remove(a[l++]);
        while(q[i].l<l)
            add(a[--l]);
        while(q[i].r>r)
            add(a[++r]);
        while(q[i].r<r)
            remove(a[r--]);
        ans[q[i].id]=getans();
    }
}


int main()
{
    //预处理
    blocksize=sqrt(MAXN);
    for(int i=1;i<MAXN;i++){
        block[i]=(i-1)/blocksize+1;
        if(blocknum!=block[i]){
            br[blocknum]=i-1;
            blocknum=block[i];
            bl[blocknum]=i;
        }
    }
    br[blocknum]=MAXN-1;
    
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    scanf("%d",&Q);
    for(int i=0;i<Q;i++){
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    sort(q,q+Q,cmp);
    solve();
    for(int i=0;i<Q;i++)
        printf("%d\n",ans[i]);
    
    return 0;
}

 

线段树代码

#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <memory.h>
#include <bitset>
#include <map>
#include <deque>
#include <math.h>
#include <stdio.h>
using namespace std;
typedef long long int ll;
const int MAXN = 500005;
const int INF=0x3f3f3f3f;

int N,Q;
int a[MAXN];
int pos[MAXN];
int ans[MAXN];

struct query{
    int l,r,id;
}q[MAXN];
bool cmp(const query &a,const query &b){
    if(a.r==b.r)
        return a.l<b.l;
    return a.r<b.r;
}

pair<int,int> tree[MAXN<<2];//前一个的位置,指向它的位置
void pushup(int rt){
    tree[rt]=min(tree[rt<<1],tree[rt<<1|1]);
}

void update(int L,int C,int l,int r,int rt){
    if(l==r){
        tree[rt].first=C;
        tree[rt].second=L;
        return;
    }
    int m=(l+r)/2;
    if(L<=m)
        update(L,C,l,m,rt<<1);
    else
        update(L,C,m+1,r,rt<<1|1);
    pushup(rt);
}

pair<int,int> query(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R)
        return tree[rt];
    pair<int,int> ans;
    ans.first=INF;
    int m=(l+r)/2;
    if(L<=m)
        ans=min(ans,query(L,R,l,m,rt<<1));
    if(R>m)
        ans=min(ans,query(L,R,m+1,r,rt<<1|1));
    return ans;
}


void solve(){

    int cur=1;
    for(int i=0;i<Q;i++){
        
        for(cur;cur<=q[i].r;cur++){
            if(pos[a[cur]])//之前已经出现过,记得把之前那个位置的答案置为正无穷
                update(pos[a[cur]],INF,1,N,1);
            update(cur,pos[a[cur]],1,N,1);
            pos[a[cur]]=cur;
        }
        
        pair<int,int> t=query(q[i].l,q[i].r,1,N,1);
        if(t.first<q[i].l)
            ans[q[i].id]=a[t.second];//用second来计算答案,因为前一次出现位置可能为0
    }
}

int main()
{
    scanf("%d",&N);
    for(int i=1;i<=N;i++)
        scanf("%d",&a[i]);
    scanf("%d",&Q);
    for(int i=0;i<Q;i++){
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
    }
    sort(q,q+Q,cmp);
    solve();
    for(int i=0;i<Q;i++)
        printf("%d\n",ans[i]);

    return 0;
}

 

主席树代码1,题解的新建节点打法

#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <memory.h>
#include <bitset>
#include <map>
#include <deque>
#include <math.h>
#include <stdio.h>
using namespace std;
typedef long long int ll;
const int MAXN = 500005;
const int INF=0x3f3f3f3f;
int N,Q;
int a[MAXN];
int pos[MAXN];
pair<int,int> tree[MAXN*60];//前一个的位置,指向它的位置
int ls[MAXN*60];
int rs[MAXN*60];
int T[MAXN];
int tot=0;

int newnode(int l,int r){
    ++tot;
    ls[tot]=l;
    rs[tot]=r;
    if(tree[l]<tree[r])//顺便pushup
        tree[tot]=tree[l];
    else
        tree[tot]=tree[r];
    return tot;
}

int build(int l,int r){
    if(l==r){
        ++tot;
        tree[tot].first=INF;
        tree[tot].second=0;
        return tot;
    }
    int m=(l+r)/2;
    return newnode(build(l,m),build(m+1,r));
}


int update(int L,int C,int l,int r,int rt){

    if(l==r){
        ++tot;
        tree[tot].first=C;
        tree[tot].second=L;
        return tot;
    }
    int m=(l+r)/2;
    if(L<=m)
         return newnode(update(L,C,l,m,ls[rt]),rs[rt]);
    else
        return newnode(ls[rt],update(L,C,m+1,r,rs[rt]));
}

pair<int,int> query(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R)
        return tree[rt];
    pair<int,int> ans;
    ans.first=INF;
    int m=(l+r)/2;
    if(L<=m)
        ans=min(ans,query(L,R,l,m,ls[rt]));
    if(R>m)
        ans=min(ans,query(L,R,m+1,r,rs[rt]));
    return ans;
}


int main()
{

    scanf("%d",&N);
    int cur=0;
    T[cur]=build(1,N);
    for(int i=1;i<=N;i++){
        scanf("%d",&a[i]);
        cur=T[i-1];
        if(pos[a[i]])
            cur=update(pos[a[i]],INF,1,N,cur);//根节点要重新计算
        cur=update(i,pos[a[i]],1,N,cur);
        T[i]=cur;
        pos[a[i]]=i;
    }

    scanf("%d",&Q);
    int l,r;
    for(int i=0;i<Q;i++){
        scanf("%d%d",&l,&r);
        pair<int,int> t=query(l,r,1,N,T[r]);
        if(t.first<l)
            printf("%d\n",a[t.second]);
        else
            printf("0\n");
    }

    return 0;
}

 

主席树,引用打法,时间跟上面那个一样

#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#include <memory.h>
#include <bitset>
#include <map>
#include <deque>
#include <math.h>
#include <stdio.h>
using namespace std;
typedef long long int ll;
const int MAXN = 500005;
const int INF=0x3f3f3f3f;
int N,Q;
int a[MAXN];
int pos[MAXN];

pair<int,int> tree[MAXN*60];//前一个的位置,指向它的位置
int ls[MAXN*60];
int rs[MAXN*60];
int T[MAXN];
int tot=0;

void pushup(int rt){
    tree[rt]=min(tree[ls[rt]],tree[rs[rt]]);
}

void build(int l,int r,int &rt){
    rt=++tot;
    if(l==r){
        tree[rt].first=INF;
        return;
    }
    int m=(l+r)/2;
    build(l,m,ls[rt]);
    build(m+1,r,rs[rt]);
    pushup(rt);
}

void update(int L,int C,int l,int r,int &rt,int lrt){
    rt=++tot;
    ls[rt]=ls[lrt];
    rs[rt]=rs[lrt];
    if(l==r){
        tree[rt].first=C;
        tree[rt].second=L;
        return;
    }
    int m=(l+r)/2;
    if(L<=m)
        update(L,C,l,m,ls[rt],ls[lrt]);
    else
        update(L,C,m+1,r,rs[rt],rs[lrt]);
    pushup(rt);
}

pair<int,int> query(int L,int R,int l,int r,int rt){
    if(L<=l&&r<=R)
        return tree[rt];
    pair<int,int> ans;
    ans.first=INF;
    int m=(l+r)/2;
    if(L<=m)
        ans=min(ans,query(L,R,l,m,ls[rt]));
    if(R>m)
        ans=min(ans,query(L,R,m+1,r,rs[rt]));
    return ans;
}

int main()
{
    scanf("%d",&N);
    int cur=0;
    build(1,N,T[0]);
    tree[0].first=INF;//坑
    for(int i=1;i<=N;i++){
        scanf("%d",&a[i]);
        cur=T[i-1];
        int newrt;
        if(pos[a[i]]){
            update(pos[a[i]],INF,1,N,newrt,cur);//记得重新计算根节点
            int c;
            update(i,pos[a[i]],1,N,c,newrt);
            newrt=c;
        }
        else
            update(i,pos[a[i]],1,N,newrt,cur);
        T[i]=newrt;
        pos[a[i]]=i;
    }

    scanf("%d",&Q);
    int l,r;
    for(int i=0;i<Q;i++){
        scanf("%d%d",&l,&r);
        pair<int,int> t=query(l,r,1,N,T[r]);
        if(t.first<l)
            printf("%d\n",a[t.second]);
        else
            printf("0\n");
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值