P4396 [AHOI2013] 作业

原题链接

前置知识:莫队+值域分块

题意:给你长为 n n n 的序列,多次询问,每次询问序列 [ l , r ] [l,r] [l,r] 中数值在 [ a , b ] [a,b] [a,b] 中的数的个数及数值个数。

区间询问,不带修改,支持离线,直接考虑莫队。出题人很良心的把值域控制在 [ 1 , 1 e 5 ] [1,1e5] [1,1e5],启示我们可以开个权值线段树或值域树状数组,来维护数值。然而这样的时间复杂度是 O ( n n l o g n ) O(n\sqrt n_{}logn) O(nn logn) 的,对于此题的数据范围很难跑过。

再想莫队算法是如何实现的,发现在跑莫队的过程中,实际最多只有 n m n\sqrt m nm 次修改和 m m m 次询问,我们想要平衡这两个过程,就可以应用值域分块。按值域分块,单点加减是可以 O ( 1 ) O(1) O(1) 做到的,并且实现很容易。对于查询,就 O ( n ) O(\sqrt n) O(n ) 的常规查询即可。

n n n m m m 同阶,故经过值域分块的平衡后,此题的时间复杂度就被我们优化到 O ( n n ) O(n\sqrt n) O(nn )

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int read(){
	int x=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,m,w[N],L[N],R[N],belong[N],cnt,sz[N],num[N],vis[N],pos[N];
struct node{
	int l,r,a,b,id;
}q[N];
struct Node{
	int sum1,sum2;
}ans[N];
bool cmp(node a,node b){
	if(pos[a.l]==pos[b.l]) return a.r<b.r;
	return a.l<b.l;
}
void add(int x){
	int t=belong[x];
	if(!vis[x]) num[t]++;
	vis[x]++,sz[t]++;
}
void del(int x){
	int t=belong[x];
	if(vis[x]==1) num[t]--;
	vis[x]--,sz[t]--;
}
Node query(int l,int r){
	int p=belong[l],q=belong[r];
	if(p==q){
		int sum1=0,sum2=0;
		for(int i=l;i<=r;i++) if(vis[i]) sum2++,sum1+=vis[i];
		return Node{sum1,sum2};
	}
	int sum1=0,sum2=0;
	for(int i=l;i<=R[p];i++) if(vis[i]) sum2++,sum1+=vis[i];
	for(int i=L[q];i<=r;i++) if(vis[i]) sum2++,sum1+=vis[i];
	for(int i=p+1;i<=q-1;i++) sum1+=sz[i],sum2+=num[i];
	return Node{sum1,sum2};
}
void init(){
	int nn=1e5,t=sqrt(nn),cnt=nn/t;if(nn%cnt) cnt++;
	for(int i=1;i<=cnt;i++) L[i]=(i-1)*t+1,R[i]=i*t;
	R[cnt]=nn;
	for(int i=1;i<=nn;i++) for(int j=L[i];j<=R[i];j++) belong[j]=i;
}
int main(){
	init();
	n=read(),m=read();
	for(int i=1;i<=n;i++) w[i]=read();
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].a=read(),q[i].b=read(),q[i].id=i;
	int t=sqrt(n);for(int i=1;i<=n;i++) pos[i]=(i-1)/t+1;
	sort(q+1,q+1+m,cmp);
	for(int i=1,l=0,r=0;i<=m;i++){
		while(l<q[i].l) del(w[l++]);
		while(r>q[i].r) del(w[r--]);
		while(l>q[i].l) add(w[--l]);
		while(r<q[i].r) add(w[++r]);
		ans[q[i].id]=query(q[i].a,q[i].b);
	}
	for(int i=1;i<=m;i++) cout<<ans[i].sum1<<" "<<ans[i].sum2<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值