[AHOI 2013]作业(分块&莫队)
作业 [做题笔记]
题目描述
给定一个 \(n\) 和序列 \(S_1,S_2,S_3,\dots S_n\),给出 \(T\) 次询问,每次 \(l,r,a,b\),对于 \(i∈[l,r]\),求当 \(S_i∈[a,b]\) 时,\(i\) 的个数和 \(S_i\) 的个数。
\(n \leq 10^5 ,T \leq 10^6\)
做题历程 (废话可忽略)
首先记录一下机房语录:
当我询问机房dalao这题纯用分块可不可过:
@CuFeO4:这题用莫队套树状数组。
@xrlong:这题用莫队套分块。
\(for(int\ i=1;i<=114514;i++)\) \(\{\)
$\ \ \ \ $@CuFeO4:树状数组好写!
$\ \ \ \ $@xrlong:分块快!
\(\}\)
@xrlong:你树状数组容易被卡
@CuFeO4:我不管反正树状数组好写,而且这题没卡我
@xrlong:加强版数据卡树状数组
\(\dots \ \dots\)
但是我看着这个\(nb\)的题目,我想挑战一下用纯分块做法——
假做法1 时空复杂都可过,但是第二问想假了
我推了半节晚三,推出一个很真的假做法:
- 对于第一问,规定 \(sumless[i][j]\) 为前\(i\)块比\(j\)小的数的个数,预处理 \(O(n \times tot)\),这个预处理很显然:
要求 \([l,r]\) 中大于等于 \(a\),小于等于 \(b\),的数的个数,设 \(vl,vr\) 为左右端点所在块,那么大块中的结果:
\([vl+1,vr-1]\) 中小于 \(a\) 的:
\([vl+1,vr-1]\) 中小于等于 \(b\) 的:
则大块中结果:
然后再跑散块即可。显然第一问没有问题。
- 第二问我如法炮制,规定 \(numless[i][j]\) 为前\(i\)块比\(j\)小的不一样数的种数,求法同上,但是显然假了。因为一个符合条件的数很可能在 \([1,vl]\) 和 \([vl,vr]\) 中同时出现,而用上文直接相减显然会假。
假代码(记录我的愚蠢)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define M 320
#define read read()
#define pt puts("")
inline int read
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
return;
}
int n,T;
int s[N];
int len,tot,v[N],L[M],R[M];
int sumless[M][N];
int numless[M][N];
int cnt[M][N];
bool have[M][N];
void init()
{
len=sqrt(n);
tot=(n-1)/len+1;
for(int i=1;i<=tot;i++){
L[i]=R[i-1]+1;
R[i]=L[i]+len-1;
}
R[tot]=n;
for(int i=1;i<=tot;i++){
for(int j=L[i];j<=R[i];j++){
v[j]=i;
cnt[i][s[j]]++;
have[i][s[j]]=1;
}
for(int j=1;j<=n;j++){
cnt[i][j]+=cnt[i-1][j];
have[i][j]|=have[i-1][j];
}
}
for(int i=1;i<=tot;i++){
for(int j=1;j<=n;j++){
sumless[i][j]=sumless[i][j-1]+cnt[i][j];
numless[i][j]=numless[i][j-1]+have[i][j];
}
}
}
bool vis[N];
void query(int l,int r,int a,int b)
{
int sum=0,num=0;
int vl=v[l],vr=v[r];
if(vr-vl<=1){
for(int i=l;i<=r;i++){
if(s[i]>=a&&s[i]<=b){
sum++;
if(!vis[s[i]])
num++,vis[s[i]]=1;
}
}
for(int i=l;i<=r;i++) vis[s[i]]=0;
write(sum);putchar(' ');write(num);pt;
return;
}
int A,B;
A=sumless[vr-1][a-1]-sumless[vl][a-1];
B=sumless[vr-1][b]-sumless[vl][b];
sum=B-A;
A=numless[vr-1][a-1]-numless[vl][a-1];
B=numless[vr-1][b]-numless[vl][b];
num=B-A;
for(int i=l;i<=R[vl];i++){
if(s[i]>=a&&s[i]<=b){
sum++;
if(!(cnt[vr-1][s[i]]-cnt[vl][s[i]])&& !vis[s[i]])
num++,vis[s[i]]=1;
}
}
for(int i=r;i>=L[vr];i--){
if(s[i]>=a&&s[i]<=b){
sum++;
if(!(cnt[vr-1][s[i]]-cnt[vl][s[i]])&& !vis[s[i]])
num++,vis[s[i]]=1;
}
}
for(int i=l;i<=R[vl];i++) vis[s[i]]=0;
for(int i=r;i>=L[vr];i--) vis[s[i]]=0;
write(sum);putchar(' ');write(num);pt;
return;
}
signed main()
{
n=read,T=read;
for(int i=1;i<=n;i++) s[i]=read;
init();
int l,r,a,b;
while(T-->0)
{
l=read,r=read,a=read,b=read;
query(l,r,a,b);
}
return 0;
}
假做法2 牺牲空间,但由于看错 \(T\) 的范围,狂 \(T\) 不止
第一问同上,第二问我考虑用 \(num[i][j][k]\) 来表示第 \(i\) 块到第 \(j\) 块小于等于 \(k\) 的数的种数,空间 \(O(n \times tot^2)\),为了卡过 \(10^8\) ,算出块长可为 \(3000\) ,我本以为 \(T\) 和 \(n\) 同一个数量级,时间 \(O(10^9)\) 可以卡过 \(10000ms\) 时限,然后 \(TLE\) ,我才发现
for(int i=1;i<=tot;i++){
for(int j=1;j<=n;j++)
sumless[i][j]=sumless[i][j-1]+cnt[i][j];
for(int j=i;j<=tot;j++){
for(int k=1;k<=n;k++){
numless[i][j][k]=numless[i][j][k-1];
if(cnt[j][k]-cnt[i-1][k]) numless[i][j][k]++;
}
}
}
又挂了,于是我果断—— 贺 学习莫队
正解(莫队&分块)
初学莫队,不得不赞叹,莫队太屌了,我们不再对原序列分块,我们对所有询问排序并分块,类似于一个区间从左往右扫,挨个解决每个询问
核心就这几行:
for(int i=1;i<=m;i++){
while(ql<q[i].l) del(s[ql++]);
while(ql>q[i].l) add(s[--ql]);
while(qr<q[i].r) add(s[++qr]);
while(qr>q[i].r) del(s[qr--]);
//到底是 ++ql,还是ql++,可以推一下原因
ans1[q[i].id]=query1(q[i].a,q[i].b);
ans2[q[i].id]=query2(q[i].a,q[i].b);
}
我们对值域分块,设:
- \(cnt[x]\) 表示当前区间内 \(x\) 出现次数
- \(spx1[i]\) 表示第 \(i\) 块中数的个数
- \(spx2[i]\) 表示第 \(i\) 块中不同数的种数
修改即:
void add(int x)
{
if(!cnt[x]) spx2[v[x]]++;
spx1[v[x]]++;
cnt[x]++;
}
void del(int x)
{
cnt[x]--;
spx1[v[x]]--;
if(!cnt[x]) spx2[v[x]]--;
}
当我们跑到查询区间 \([q_i.l,q_i.r]\) 时,直接分块思想查询 \([q_i.a,q_i.b]\) 即可。
int query1(int a,int b)
{
int res=0;
int va=v[a],vb=v[b];
if(vb-va<=1){
for(int i=a;i<=b;i++) res+=cnt[i];
return res;
}
for(int i=va+1;i<=vb-1;i++)
res+=spx1[i];
for(int i=a;i<=R[va];i++) res+=cnt[i];
for(int i=b;i>=L[vb];i--) res+=cnt[i];
return res;
}
int query2(int a,int b)
{
int res=0;
int va=v[a],vb=v[b];
if(vb-va<=1){
for(int i=a;i<=b;i++) res+=(cnt[i]>0);
return res;
}
for(int i=va+1;i<=vb-1;i++)
res+=spx2[i];
for(int i=a;i<=R[va];i++) res+=(cnt[i]>0);
for(int i=b;i>=L[vb];i--) res+=(cnt[i]>0);
return res;
}
\(AC\ \ code\)
#include<bits/stdc++.h>
using namespace std;
#define N 100010
#define T 350
#define M 1000010
#define read read()
#define pt puts("")
inline int read
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return f*x;
}
void write(int x)
{
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
return;
}
int n,m;
int s[N];
int len,tot,v[N],L[T],R[T];
int spx1[T],spx2[T];
int ans1[M],ans2[M];
int cnt[N];
struct Q{
int l,r,a,b,id;
}q[M];
bool cmp(Q a,Q b)
{
if(v[a.l]==v[b.l]) return a.r<b.r;
return a.l<b.l;
}
void init()
{
len=sqrt(n);
tot=(n-1)/len+1;
for(int i=1;i<=tot;i++){
L[i]=R[i-1]+1;
R[i]=L[i]+len-1;
}
R[tot]=n;
for(int i=1;i<=tot;i++)
for(int j=L[i];j<=R[i];j++)
v[j]=i;
}
void add(int x)
{
if(!cnt[x]) spx2[v[x]]++;
spx1[v[x]]++;
cnt[x]++;
}
void del(int x)
{
cnt[x]--;
spx1[v[x]]--;
if(!cnt[x]) spx2[v[x]]--;
}
int query1(int a,int b)
{
int res=0;
int va=v[a],vb=v[b];
if(vb-va<=1){
for(int i=a;i<=b;i++) res+=cnt[i];
return res;
}
for(int i=va+1;i<=vb-1;i++)
res+=spx1[i];
for(int i=a;i<=R[va];i++) res+=cnt[i];
for(int i=b;i>=L[vb];i--) res+=cnt[i];
return res;
}
int query2(int a,int b)
{
int res=0;
int va=v[a],vb=v[b];
if(vb-va<=1){
for(int i=a;i<=b;i++) res+=(cnt[i]>0);
return res;
}
for(int i=va+1;i<=vb-1;i++)
res+=spx2[i];
for(int i=a;i<=R[va];i++) res+=(cnt[i]>0);
for(int i=b;i>=L[vb];i--) res+=(cnt[i]>0);
return res;
}
signed main()
{
n=read,m=read;
for(int i=1;i<=n;i++) s[i]=read;
init();
for(int i=1;i<=m;i++){
q[i].id=i;q[i].l=read;q[i].r=read;q[i].a=read;q[i].b=read;
}
sort(q+1,q+m+1,cmp);
int ql=1,qr=0;
for(int i=1;i<=m;i++){
while(ql<q[i].l) del(s[ql++]);
while(ql>q[i].l) add(s[--ql]);
while(qr<q[i].r) add(s[++qr]);
while(qr>q[i].r) del(s[qr--]);
ans1[q[i].id]=query1(q[i].a,q[i].b);
ans2[q[i].id]=query2(q[i].a,q[i].b);
}
for(int i=1;i<=m;i++){
write(ans1[i]);
putchar(' ');
write(ans2[i]);
pt;
}
return 0;
}