【AHOI2013】作业题解

P4396 [AHOI2013] 作业

题目描述

此时己是凌晨两点,刚刚做了 Codeforces 的小 A 掏出了英语试卷。英语作业其实不算多,一个小时刚好可以做完。然后是一个小时可以做完的数学作业,接下来是分别都是一个小时可以做完的化学,物理,语文……小 A 压力巨大。

这时小 A 碰见了一道非常恶心的数学题,给定了一个长度为 \(n\) 的数列和若干个询问,每个询问是关于数列的区间表示数列的第 \(l\) 个数到第 \(r\) 个数),首先你要统计该区间内大于等于 \(a\),小于等于 \(b\) 的数的个数,其次是所有大于等于 \(a\),小于等于 \(b\) 的,且在该区间中出现过的数值的个数。

小 A 望着那数万的数据规模几乎绝望,只能向大神您求救,请您帮帮他吧。

输入格式

第一行两个整数 \(n,m\)

接下来 \(n\) 个不超过 \(10^5\) 的正整数表示数列

接下来 \(m\) 行,每行四个整数 \(l,r,a,b\),具体含义参见题意。

输出格式

输出 \(m\) 行,分别对应每个询问,输出两个数,分别为在 \(l\)\(r\) 这段区间中大小在 \([a,b]\) 中的数的个数,以及大于等于 \(a\),小于等于 \(b\) 的,且在该区间中出现过的数值的个数(具体可以参考样例)。

输入输出样例 #1

输入 #1

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

输出 #1

2 2
1 1
3 2
2 1

说明/提示

\(N\leq 100000,M\leq 100000\),读入的数字均为 \([1,10^5]\) 内的正整数。

思路:发现有询问区间,所以想到用离线莫队,其次他询问的可以转化为一段“区间”的“种类”和“个数”,所以涉及单点修改与区间查询,因此可以想到用莫队来移动L,R两个指针,修改与查询再用一个朴素分块。

AC code

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
const int maxn=1e6+10;
const int maxm=1e5+10;
//试试分块+莫队,1维护莫队,2维护分块 (a2下标代表值,a2i代表i这个值有多少个) 
int n,m,L[3010],R[3010],cnt1,cnt2,h=1,t,pos1[maxm],pos2[maxm],a1[maxm],a2[maxm];
struct nd1
{
	int x,y,a,b,id;
}q[maxn];//操作结构体 
struct nd2
{
	int l,r,sum,tot;//sum代表数量,tot代表“种类”数 
}v[3010];//不想用一堆一维数组维护分块,所以用结构体 
struct nd3
{
	int sum,tot;
}ans[maxn];//答案结构体 
bool cmp(nd1 op,nd1 po)
{
	if(pos1[op.x]!=pos1[po.x]) return pos1[op.x]<pos1[po.x];
	if((pos1[op.x]&1)==1) return op.y<po.y;
	return op.y>po.y;
}// 奇偶排序 
void init1()
{
	int B=n/sqrt(m)+1;//记得加1,防止为零 
	cnt1=n/B;
	for(int i=1;i<=cnt1;i++)
	{
		L[i]=(i-1)*B+1;
		R[i]=i*B;
	}
	if(R[cnt1]<n)
	{
		cnt1++;
		L[cnt1]=R[cnt1-1]+1;
		R[cnt1]=n;
	}
	for(int i=1;i<=cnt1;i++)
	{
		for(int j=L[i];j<=R[i];j++)
		{
			pos1[j]=i;
		}
	}
}// 为莫队分块 
void init2()
{
	cnt2=sqrt(1e5);//值域恒定,或者说不知道具体值域 
	for(int i=1;i<=cnt2;i++)
	{
		v[i].l=(i-1)*cnt2+1;
		v[i].r=i*cnt2;
	}
	if(v[cnt2].r<1e5)
	{
		cnt2++;
		v[cnt2].l=v[cnt2-1].r+1;
		v[cnt2].r=1e5;
	}
	for(int  i=1;i<=cnt2;i++)
	{
		for(int j=v[i].l;j<=v[i].r;j++)
		{
			pos2[j]=i;
		}
	}
}//为朴素分块分块 (bushi 
void update(int x,int d)
{
	a2[x]+=d;
	v[pos2[x]].sum+=d;
	if(d>0&&a2[x]==1)v[pos2[x]].tot++;//add
	else if(d<0&&a2[x]==0) v[pos2[x]].tot--;//del
}//用分块的update取代add,del.也可以分开用add,del 
nd3 query(int l,int r)//返回一个结构体 
{
	nd3 fg;
	fg.sum=0;
	fg.tot=0;//一定要初始化 
	int p=pos2[l],q=pos2[r];
	if(p==q)//块内挨个暴力 
	{
		for(int i=l;i<=r;i++)
		{
			if(a2[i]>0)
			{
				fg.sum+=a2[i];
				fg.tot++;
			}
		}
	}
	else//块外整块直接用 
	{
		for(int i=p+1;i<q;i++)
		{
			fg.sum+=v[i].sum;
			fg.tot+=v[i].tot;
		}
		for(int i=l;i<=v[p].r;i++)
		{
			if(a2[i]>0)
			{
				fg.sum+=a2[i];
				fg.tot++;
			}
		}
		for(int i=v[q].l;i<=r;i++)
		{
			if(a2[i]>0)
			{
				fg.sum+=a2[i];
				fg.tot++;
			}
		}
	}
	return fg;
}
signed main()
{
//	freopen("1.in","r",stdin);
//	freopen("2.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a1[i];
	init1();
	init2();//分块 
	for(int i=1;i<=m;i++)
	{
		cin>>q[i].x>>q[i].y>>q[i].a>>q[i].b;
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);//排序 
	for(int i=1;i<=m;i++)
	{//莫队经典,只不过与分块结合了一下 
		while(h<q[i].x){update(a1[h],-1);h++;}
		while(h>q[i].x){h--;update(a1[h],1);}
		while(t<q[i].y){t++;update(a1[t],1);}
		while(t>q[i].y){update(a1[t],-1);t--;}
		ans[q[i].id]=query(q[i].a,q[i].b);
	}
	for(int i=1;i<=m;i++) cout<<ans[i].sum<<" "<<ans[i].tot<<'\n';//按顺序输出即可
	return 0;
}


posted @ 2026-07-15 21:20  wang7979  阅读(8)  评论(0)    收藏  举报