Luogu4161 & Luogu6280 - dp - 数论 -
对于某个排列p,将\(i \rightarrow p_i\)建个图发现这时对应的答案就是\(lcm(每个环的大小)\),注意与环中哪些数无关。
进一步观察可以发现这实际上就是这个问题:从\(n\)的排列中分成\(m\)个部分,令\(p=\)这\(m\)个部分的size的lcm,求不同p的和/个数
看到lcm可以考虑质因子
先考虑求p的个数:
首先,想让p的个数最大的话,肯定是让size都互质(注意由于可以有多个1,所以size之和可以小于n)(如果两两不互质的话,显然可以这两个数同除gcd,显然这样的话答案一定不会变差,所以以下均默认两两互质,注意1也是互质)
问题就转化为了求这个方案数:\(p_1^{t_1}+p_2^{t_2}+..+p_k^{t_k} \leq n\),\(p_i\)是从小到大的质数,此时\(K=lcm(p_1^{t_1}, ..., p_k^{t_k})=所有的相乘\)
这个显然可以dp,设\(dp[i][j]\)表示考虑到前i个质数,当前和为j的方案数,\(dp[0][0] = 1\)
考虑到\(dp[i][j] = \sum_{k=1}^{p*k\leq j}dp[i-1][j - p^k]\)(这个转移就相当于加了一个size为\(p^k\)的集合,其\(K=lcm(..)\)多乘了个\(p^k\))
答案就是\(dp[pcnt][1..n]\)(因为1的缘故和可以小于n)
至于算和,转移改成\(dp[i][j] = dp[i-1][j] + \sum_{k=1}^{p^k\leq j}dp[i-1][j - p^k]*p^k\)即可(因为相当于此时答案中所有的K都乘了个\(p^k\),因为之前p没有出现过,lcm直接相乘)
代码(6280):
// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
typedef long long LL;
const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 10005;
LL dp[2][maxn];
int n,notpm[maxn], pm[maxn], pcnt=0;
void xxs(){
notpm[1] = 1;
for(int i=2;i<=n;i++){
if(!notpm[i])pm[++ pcnt] = i;
for(int j=1;j<=pcnt && i*pm[j] <= n;j++){
notpm[i*pm[j]] = 1;
if(i%pm[j] == 0)break;
}
}
}
signed main(){
int mod;
scanf("%d%d",&n,&mod);
xxs();
dp[0][0] = 1;
for(int i=1;i<=pcnt;i++){
memcpy(dp[i&1], dp[i&1^1], sizeof dp[i&1]);
int pp = pm[i];
while(pp <= n){
for(int j=pp;j<=n;j++)
(dp[i&1][j] += 1ll * dp[i&1^1][j - pp] * pp) %= mod;
pp *= pm[i];
}
}
LL ans = 0;
for(int i=1;i<=n;i++)(ans += dp[pcnt&1][i])%=mod;
printf("%lld\n",ans + 1);
return 0;
}