ABC335F根号分治做法
题意翻译:
- 有 \(N\) 个格子。
- 你初始在格子 \(1\)。
- 格子 \(1\) 是染黑的,其他的格子都是白的。
- 当你在格子 \(i\) 的时候,你可以到达 \(a_i\times x+i,x>0\) 或将该格子染黑。
- 求所有格子的状态有多少种情况。
首先我们来考虑一下不加优化的 dp。
对于任意一个格子 \(i\ne1\),都可以是白的,不妨设格子 \(1\) 也可以是白的,然后将答案除以 \(2\)。
dp[i]
统计的是对于一个格子 \(i\) 可以到达其他格子的染色的情况,转移方程为 \(dp_i=\sum\limits_{j,a_j\mid(i-j)}dp_j+2\)。
这个 dp 是倒着跑的。
好像不是很好优化啊,一时间想不出来数据结构。
赛事用了反着的前缀和可以让两个 TLE -> AC。
能不能用每几个数求和的类似于前缀和的东西,优化一下。
如果不论每多少数都求这个东西复杂度会炸掉。
可以隔的数的数量最大到 \(\sqrt N\),此时在空间时间都会平衡一些。
定义一个二维的 sum[i][j]
,存的是在某点之后的 \(\sum\limits_{dp_{i\times x+j},x\ge 0}\),跑 dp 的时候维护一下,如果 \(a_i\le\sqrt N\) 就直接加一下,否则暴力。
这样的做法是 \(N\sqrt N\) 的。
代码如下。
#include<bits/stdc++.h>
using namespace std;
constexpr int MAXN=2e5+10,MAXSQRTN=500,MOD=998244353;
int n,a[MAXN],dp[MAXN],sum[MAXSQRTN][MAXSQRTN];
template<typename T>
T read(){
T f=1,x=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^48);ch=getchar();}
return f*x;
}
namespace sol{
void solve(){
n=read<int>();
for(int i=1;i<=n;++i){
a[i]=read<int>();
}
int T=sqrt(n);
for(int i=n;i;--i){
dp[i]=2;
if(a[i]<=T){
dp[i]+=sum[a[i]][i%a[i]];
dp[i]%=MOD;
}
else for(int j=i+a[i];j<=n;j+=a[i]){
dp[i]+=dp[j];
dp[i]%=MOD;
}
for(int j=1;j<=T;++j){
int temp=i%j;
sum[j][temp]+=dp[i];
sum[j][temp]%=MOD;
}
}
dp[1]=1ll*dp[1]*499122177%MOD;
printf("%d\n",dp[1]);
}
}
int main(){
sol::solve();
return 0;
}