P7960 [NOIP2021] 报数 题解
思路
考虑先确定一个数可不可以被报出来。
考虑递归定义布尔函数 \(f(x)\) 表示 \(x\) 不可以报出来,那么:
\[\begin{cases}
1, &f(x\bmod 10)\land x\bmod 10\neq 7
\\
1, &\forall y|x\land f(y)
\\
0,&other
\end{cases}
\]
读者可以思考一下第一个条件的正确性。
那么现在我们要算所有 \(x\) 在 \(10^7\) 的 \(f(x)\),怎么算呢?
首先这个东西不用搞递归,容易发现 \(f(x\bmod 10)\) 或者 \(\forall y|x\) 中的 \(f(y)\) 会比 \(x\) 先算到,所以可以直接 \(O(n)\) 递推。
然后呢?然后为了标记对于每个 \(y\) 的所有倍数,我们要搞一个均摊 \(\log n\) 的循环去标记。对于任意一个 \(i\),若 \(vis_i=0\land f(i)\),那么就要把 \(i\) 的倍数 \(j(j\geqslant1)\) 标记为 \(vis_j=1\)。
这一部分做完之后,我们已经用 \(n\log n\) 的时间复杂度算出了任意一个数是否可行。那怎么处理下一个数要报多少呢?
考虑递推,因为 \(vis_i\) 被预处理出来表示这个数可不可行,所以若 \(x_i\) 表示 \(i\) 的下一个可报出数,那么:
\[\begin{cases}
x_i=x_{i+1},&vis_{i+1}=0
\\x_i=i+1,&vis_{i+1}=1
\end{cases}
\]
为了保证正确性,从后往前递推即可。
代码
先 \(n\log n\) 处理 \(vis_i\),像在思路中提到的一样:
for(int i=1;i<=n;i++){
if(cannot[i/10]||i%10==7){
cannot[i]=1;
}
if(cannot[i]&&!vis[i]){
vis[i]=1;
for(int j=i+i;j<=n;j+=i){
vis[j]=1;
}
}
}
这里的 \(n\) 取 \(10^7+1\)。下同。
然后像思路中所述的 \(O(n)\) 递推即可:
for(int i=n;i>=1;i--){
if(vis[i+1]){
ans[i]=ans[i+1];
}
else{
ans[i]=i+1;
}
}
最后答案都被存进 \(ans\) 里面了,对于每个询问,输出对应的 \(ans\)。当然若对于每个询问,对应的 \(vis\) 是 \(1\) 的话就要输出 \(-1\) 了。