【题解】广大附中2020提高组十连测(四)T3——计算
计算
(标签:动态规划、数学、玄学、毒瘤)
题目描述
给定 \(n\) ,求合法的 \((x_1,x_2,x_3, \ldots ,x_{2m})\) 组数。一组 \(x\) 是合法的,当且仅当:
- \(\forall i \in [1,2m],\ x_i \in \mathbb{Z}^+,\ x_i|n\)
- \(\prod\limits_{i=1}^{2m}{x_i} \leq n^m\)
合法的 \((x_1,x_2,x_3, \ldots ,x_{2m})\) 可能会有很多,请输出答案 \(mod\ 998244353\)
输入格式
一行由空格隔开的两个整数,分别是 \(n\) 和 \(m\)。
输出格式
一行表示答案。
样例
输入样例 1
6 1
输出样例 1
10
输入样例 2
6 3
输出样例 2
2248
样例解释
第一个样例中,合法的方案有 \((1, 1),\ (1, 2),\ (1, 3),\ (1, 6),\ (2, 1),\ (2, 2),\ (2, 3),\ (3, 1),\ (3, 2),\ (6, 1)\) 共 \(10\) 种。
数据范围
对于 \(20\%\) 的数据 \(n \leq 50,\ m=2\)
对于 \(60\%\) 的数据 \(n \leq 100,\ m \leq 3\)
对于 \(100\%\) 的数据 \(n \leq 10^9,\ m \leq 100\)
题目分析
这道题一看大概就可以看出来是推公式,因为 \(1e9\) 的数据范围不可能是什么其它的方法
首先我们设 \(F(i)=\prod\limits_{i=1}^{2m}\ x_i,\ G(i)=\prod\limits_{i=1}^{2m}\ \frac{n}{x_i}\)
那么我们就可以开始玄学推导了,下面的内容请坚持看完别晕倒或睡着:|
---------------分----------隔----------线---------------
(\(\sigma(n)\) 表示 \(n\) 的质因数个数)
那么问题就转换为计算 \(S_2\)了,也就是说,计算有多少 \(F(x)=n^m\)。这个可以使用 \(DP\) 来计算。设 \(dp_{i,j}\) 表示前 \(i\) 个数满足条件的组数是 \(j\) 的个数,可以推出状态转移方程是 \(dp_{i,j}=\sum\limits_{k=0}^{w}\ dp_{i-1,j-k}\)。其中我们发现,第 \(i\) 次的转移只和第 \(i-1\) 次有关,所以我们可以使用滚动数组;计算 \(\sum\limits_{k=0}^{w}\ dp_{i-1,j-k}\) 可以使用前缀和。
最后一个,也是最玄学的优化就是,前缀和数组和 \(DP\) 数组可以使用同一个……
完整代码
#include <bits/stdc++.h>
#define MOD 998244353
#define int long long //懒人式开long long
using namespace std;
int n,m,ans,num=1,phi=1,dp[6600];
int quickPow(int a,int b) //快速幂,不解释
{
int res=1;
while(b)
{
if(b&1)
res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
void solve(int w) //DP过程
{
memset(sum,0,sizeof(dp));
dp[0]=1; //DP初始化
for(int i=1;i<=2*m;i++)
{
for(int j=1;j<=w*m;j++)
dp[j]=(dp[j-1]+dp[j])%MOD; //计算前缀和
for(int j=w*m;j>w;j--)
dp[j]=(dp[j]+MOD-dp[j-w-1])%MOD; //利用前缀和DP,这段代码的理解需要一定时间
}
phi=(phi*(w+1))%MOD; //计算n的质因数个数
num=(num*dp[w*m])%MOD; //计算公式中的S2
}
signed main()
{
scanf("%lld%lld",&n,&m);
for(int p=2;p<=sqrt(n);p++)
{
if(n%p) continue;
int a,w=0;
while(n%p==0) n/=p,w++; //一些操作,以保证搜到的是质因数
solve(w);
}
if(n>1) solve(1); //n为完全平方数的特判
ans=(quickPow(phi,2*m)+num)%MOD; //照搬公式的分子
ans=(ans%2?(ans+MOD)/2:ans/2)%MOD; //然后除以二
printf("%lld",你猜这里要输出什么);
return 0;
}