【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;
}

浙公网安备 33010602011771号