[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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!