P9809 [SHOI2006] 作业 Homework 题解

· · 题解

题目传送门:P9809 [SHOI2006] 作业 Homework

题目分析

由标签可知,本题考虑根号分治。

视值域与 n 同阶,设一个阈值 \sqrt{n}

当询问 x 时,若 x < \sqrt{n},由于只有 \sqrt{n} 种,所以在加入时暴力维护,询问直接回答即可;

x \geqslant \sqrt{n},我们枚举商 k,则 xk \leqslant nk 只有 \sqrt{n} 种。

现在的需求是 O(1) 查询在 [xk,xk+x) 中最小的出现过的元素。

将问题转化为在 [xk,+\infty) 中最小的出现过的元素是否小于 xk+x

聚集一个点 i,能对其贡献的 x 是所有大于等于 i 的。因此在加入 x 时等价于对 [1,x] 取一个前缀 \min

分块。由于这个东西肯定是单调不降的,所以一定存在一个块使得左右端点分居 x 两侧。对这个块以及 x 初始的块暴力更新,其他的打个标记就行了。

代码实现

$f_2$:块的标记; $f_3$:块内部的答案。 ```cpp while(n--){ char opt; int x; cin>>opt>>x; if(opt=='A'){ mn=min(mn,x); for(int i=1;i<lim;i++)f1[i]=min(f1[i],x%i); for(int i=L[b[x]];i<=x;i++)f3[i]=min(f3[i],x); for(int i=b[x]-1;i>=0;i--){ if(f2[i]<=x)break; f2[i]=x; } } else{ if(x<lim)cout<<f1[x]<<'\n'; else{ int ans=mn; for(int i=0;i*x<N;i++){ int l=i*x,r=min((i+1)*x,N)-1; f3[l]=min(f3[l],f2[b[l]]); if(f3[l]<=r)ans=min(ans,f3[l]-i*x); } cout<<ans<<'\n'; } } } ``` [AC 记录](https://www.luogu.com.cn/record/275609750)。