ABC335F根号分治做法

发布时间 2024-01-06 22:10:29作者: LiJoQiao

题意翻译:

\(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>=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;
}