P3861 拆分 题解

发布时间 2023-12-20 09:04:29作者: jr_inf

阅读了多遍 @WJiannan 的题解,还是有很多不理解的地方,翻新一下。

新奇 dp 题。

暴力地,令 \(dp_{i,j}\) 为将 \(i\) 拆分为任意个不大于 \(j\) 的因数之积的方案数,则有 \(dp_{i,j}=dp_{i,j-1}+\sum_{k|i}dp_{k,j-1}\)

发现对于任意对 \(dp_{n,n}\) 有贡献的 \(dp_{k,j-i}\) 都满足 \(k|n\),考虑舍弃无用状态。

\(n\) 升序分解因数得到序列 \(p\)。令 \(dp_{i,j}\) 为将 \(p_i\) 拆分为任意个不大于 \(p_j\) 的因数之积的方案数,则有 \(dp_{i,j}=dp_{i,j-1}+\sum_{p_k | p_i} dp_{k,j-1}\),相当于只选了一个 \(p_j\) 或者不选的方案数之和。

具体到实现,枚举 \(k\)\(j\),计算 \(dp_{k,j}\) 对于 \(dp_{i,j+1}\) 的贡献,其中 \(i=p_k \times p_{j+1}\),发现 \(k\) 不变时,\(i\)\(j\) 增大而增大,在增大 \(j\) 的同时维护 \(i\) 可以节省时间。令 \(m\)\(n\) 的因数个数,时间复杂度 \(O(m^2)\)\(m \leq 6720\) 足以通过此题。

code:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N=7000+10,mod=998244353;
int	t,n,p[N],dp[N][N],qp;
signed main()
{
	scanf("%d",&t);
	while(t--)
	{
		qp=0;
		memset(dp,0,sizeof(dp));
		scanf("%lld",&n);
		for(int i=1;i*i<=n;i++)
		{
			if(n%i==0)
			{
				qp++,p[qp]=i;
				if(n/i!=i)qp++,p[qp]=n/i;
			}
		}
		sort(p+1,p+1+qp);
		p[qp+1]=n*4;
		dp[1][1]=1;
		for(int k=1;k<=qp;k++)
		{
			int i=k+1;
			for(int j=2;j<=qp;j++)//按照上文,这里代表 j+1
			{
				if(p[j]*p[k]<=n)
				{
					while(p[i+1]<=p[j]*p[k])i++;
					if(p[i]==p[j]*p[k])dp[i][j]=(dp[i][j]+dp[k][j-1])%mod;
				}
				dp[k][j]=(dp[k][j]+dp[k][j-1])%mod;
			}
		}
		printf("%lld\n",dp[qp][qp]-1);
	}
}