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\) 了。

posted @ 2022-01-26 16:40  Shunpower  阅读(108)  评论(0编辑  收藏  举报