牛客练习赛10 E 数列查找 [莫队+分块]

题意:给你长度为n的数列,m次询问,每次询问求【L,R】中,是第k1个次数的第k2个数是多少。

题解:很巧妙的解法,一开始想到的是莫队+线段树,转移时间为O(m*sqrt(m)*logn),得出结果时间为O(m*logn),所以总的时间复杂度为O(m*(sqrt(m)*logn+m*logn))就TLE了,可以看出主要的时间复杂度在转移上,所以我们可以通过减少转移的时间增加查询的时间,使得总的复杂度更低,于是可以采用分块的方法,我们需要四个数组:

int num[N];//a[i]出现次数 
int bcot[205],cot[N];//次数类型的总数 
int blo[N][205];//该次数下数的个数 

所以我们只需要O(1)转移,修改次数总和块的信息、某次数下数的个数的块的信息。然后查询的时候通过O(sqrt(n))查找第k1的次数是多少,与该次数下第k2的数是多少的信息,所以我们总的复杂度为O(m*(sqrt(m)+sqrt(n))就不会TLE了。

AC代码:

#include<stdio.h>  
#include<string.h>  
#include<math.h>  
#include<algorithm>  
#include<iostream>  
#define N 50005  
using namespace std;   
int a[N];
int real[N],pos[N];  
int num[N];//a[i]出现次数 
int bcot[205],cot[N];//次数类型的总数 
int blo[N][205];//该次数下数的个数 
int len;  
struct node
{
    int l,r,k1,k2,id;
    node(){}
    node(int l,int r,int k1,int k2,int id)
    {
        this->l=r;
        this->r=r;
        this->k1=k1;
        this->k2=k2;
        this->id=id;
    }
}q[N];
int n,m;
int ans[N];
bool cmp(node a,node b)
{
    return pos[a.l]<pos[b.l]||pos[a.l]==pos[b.l]&&a.r<b.r;
}
int qcot(int need)
{
	int sum=0;
	for(int i=0;i<=len+1;i++)
	{
		sum+=bcot[i];
		if(sum>=q[need].k1)
		{
			sum-=bcot[i];
			for(int j=i*len;j<(i+1)*len;j++)
			{
				sum+=cot[j];
				if(sum>=q[need].k1)
					return j;
			}
		}
	}
	return -1;
}
int qnum(int cot,int need)
{
	int sum=0;
	for(int i=0;i<=len+1;i++)
	{
		sum+=blo[cot][i];
		if(sum>=q[need].k2)
		{
			sum-=blo[cot][i];
			for(int j=i*len;j<(i+1)*len;j++)
			{
				if(num[j]==cot)sum++;
				if(sum>=q[need].k2)
					return j;
			}
		}
	}
	while(1);
	return -1;
}
void change(int number,int flag)
{
	if(flag)
	{
		if(num[number]!=0)
	   	{
			blo[num[number]][number/len]--;
			if(real[num[number]]==1)bcot[num[number]/len]--,cot[num[number]]--;
			real[num[number]]--;
		}
		            
		blo[++num[number]][number/len]++;
		if(real[num[number]]==0)bcot[num[number]/len]++,cot[num[number]]++;
		real[num[number]]++;
	}
	else 
	{
		blo[num[number]][number/len]--;
		if(real[num[number]]==1)bcot[num[number]/len]--,cot[num[number]]--;
	   	real[num[number]]--;
            	
       	if(num[number]!=1)
		{
	       	blo[--num[number]][number/len]++;
	       	if(real[num[number]]==0)bcot[num[number]/len]++,cot[num[number]]++;
	       	real[num[number]]++;
		}
		else num[number]--;
	}
}
int main()
{
    scanf("%d",&n);
    len=(int)sqrt((double)n+0.1);
    for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
    scanf("%d",&m);
    for(int i=1;i<=n;i++)
        pos[i]=(i-1)/len+1;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d%d%d",&q[i].l,&q[i].r,&q[i].k1,&q[i].k2);
        q[i].id=i;
    }
    sort(q,q+m,cmp);
    int l=1,r=0;
    for(int i=0;i<m;i++)
    {
        if(r<q[i].r)
        {
            for(r=r+1;r<=q[i].r;r++)
            	change(a[r],1);
            r--;
        }
        if(q[i].l<l)
        {
            for(l=l-1;l>=q[i].l;l--)
            	change(a[l],1);
            l++;
        }
        if(r>q[i].r)
        {
            for(;r>q[i].r;r--)
            	change(a[r],0);
        }
        if(q[i].l>l)
        {
            for(;l<q[i].l;l++)
            	change(a[l],0);
        }
        int now=qcot(i);
        now=qnum(now,i);
        ans[q[i].id]=now;
    }
    for(int i=0;i<m;i++)  
        printf("%d\n",ans[i]);
}




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值