ABC 335 F Hop Sugoroku

题意
https://atcoder.jp/contests/abc335/tasks/abc335_f

题解
显然想到dp,我们首先会产生一个最为朴实的想法,我们设dp[i]为以第i格作为结尾的方案数。那么考虑状态转移,有:dp[i]=∑dp[j](1≤j<i,i≡j(mod a[j]))。这样的做法显然是N方的,不能通过。考虑优化,我们已知a[i]≤2e5的,那么我们考虑一点:i≡j(mod a[j])的另一种表示方法是i=j+k*aj那么这个枚举时间复杂度是O(N/a[i])的,当a[j]很大时,枚举的次数其实是很少的。而考虑dp的另外一种转移:我们现在令b[i][j]表示在模i余j时这个位置的方案数,这或许很绕,我们形式化解释一下:现在有所有的位置x,且x满足x%i==j,那么b[i][j]=∑dp[x]。注意这里的x不是指某一个位置,而是一群满足性质的位置集合这个要在过程中进行,不可预处理。
现在考虑其时间复杂度,对于现在某个要转移的dp[i],其余数个数是O(a[i])级别的,那么这个转移是O(a[i])的。所以如果我们穿插两种方法进行所有位置的状态转移,总的时间复杂度为:O(N(N/V+V)),求导可以得到最优时间复杂度:当我们设置阈值为V=sqrt(N),那么总的时间复杂度为O(Nsqrt(N)),可以通过,这也是所说的根号分治。具体实现的话对于当前要转移的dp[i],如果当前位置的a[i]大于等于V,那么我们采取第一种转移方法(可用刷表法实现);对于小于V的情况,我们采取第二种转移方法。
在转移之前,先把dp[i]用第二种方法给算出来。剩下的细节就看代码吧

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int maxn=1e6+10;
const int mmm=1e3+10;
int n;
int a[maxn],dp[maxn],b[mmm][mmm];

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	cin>>n;
	int ans=0;
	int s=sqrt(n);
	dp[1]=1;
	for(int i=1;i<=n;i++)
	{
		int a;
		cin>>a;
		for(int j=1;j<=s;j++)
		   dp[i]=(dp[i]+b[j][i%j])%mod;
		if(a<=s)  b[a][i%a]=(b[a][i%a]+dp[i])%mod;
		else 
		{
			int p=i;
			while(p+a<=n)
			{
				p+=a;
				dp[p]=(dp[p]+dp[i])%mod;
			}
		}
		ans=(ans+dp[i])%mod;
	}
	
	cout<<ans;
	
	
	return 0;
 } 
posted on 2024-07-05 20:46  Linear_L  阅读(4)  评论(0编辑  收藏  举报