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;
}


posted @ 2022-09-14 15:25  SkyRainWind  阅读(10)  评论(0编辑  收藏  举报