组合数动态规划

写在前面的话:嘤,我终于把我上次比赛的题补完了
在这里插入图片描述
题目D. Counting Factorizations
题目大意:给你一个集合含有 2 ∗ n 2*n 2n个数,问集合使用唯一分解定理能表示出多少个数?其中底数必须满足: p 1 < p 2 < p 3 < . . . < p n p_{1}<p_{2}<p{3}<...<p_{n} p1<p2<p3<...<pn
解题思路:
其实就是从多于 n n n个质数当中选出 n n n个数来,然后剩下的都作为质数,每一种方案当中质数是固定的,对指数使用排列组合,也就是 n ! c n t 1 ! ∗ c n t 2 ! . . . \frac{n!}{cnt_{1}!*cnt_{2}!...} cnt1!cnt2!...n!

首先需要把这些方案分成正交的。

我们知道组合数当中有一个经典的方程为
c ( i , j ) = c ( i − 1 , j − 1 ) + c ( i − 1 , j ) c(i,j)=c(i-1,j-1)+c(i-1,j) c(i,j)=c(i1,j1)+c(i1,j)
其中 c ( i , j ) c(i,j) c(i,j)表示的含义是从前 i i i个数中选出 j j j个符合条件的数的方案。

那么这个题,我们也可以这么来设。设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从前 i i i个数(这些数应该去重)当中选出 j j j个质数的方案数。那么当由 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i1][j1]状态转移过来的时候就代表第 i i i个数(前提是质数)应该是底数,则第 i i i个数对于指数的贡献就是 c n t [ i ] − 1 cnt[i]-1 cnt[i]1,也就是 d p [ i − 1 ] [ j − 1 ] ( c n t [ i ] − 1 ] ) ! \frac{dp[i-1][j-1]}{(cnt[i]-1])!} (cnt[i]1])!dp[i1][j1];如果是由 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]转移过来的时候就代表不选择第 i i i个数作为底数。则其对指数的贡献为 d p [ i − 1 ] [ j ] ( c n t [ i ] ] ) ! \frac{dp[i-1][j]}{(cnt[i]])!} (cnt[i]])!dp[i1][j]

所以转移方程为:

	for (int i = 1; i < kl.size(); i++)
	{
		dp[i][0] = div1(dp[i - 1][0] , fact[cnt[kl[i]]]);
		for (int j = 1; j <= min(i, n); j++)
		{
			if (prime[kl[i]])
				dp[i][j] = ((ll)div1(dp[i - 1][j - 1] ,  fact[cnt[kl[i]]-1]) +
				           div1(dp[i - 1][j] , fact[cnt[kl[i]]]))%mod;
			else
				dp[i][j] = div1(dp[i - 1][j] , fact[cnt[kl[i]]]);
		}
	}

这里需要注意每一个 d p [ i ] [ j ] dp[i][j] dp[i][j]实际上是你列的式子中的一部分,比如说,你的一种方案是 n ! c n t 1 ! ∗ c n t 2 ! . . . c n t n ! \frac{n!}{cnt_{1}!*cnt_{2}!...cnt_{n}! } cnt1!cnt2!...cntn!n!,那么 d p [ i ] [ j ] dp[i][j] dp[i][j]实际只是 1 c n t 1 ! ∗ c n t 2 ! . . . c n t j ! \frac{1}{cnt_{1}!*cnt_{2}!...cnt_{j}! } cnt1!cnt2!...cntj!1(最后再乘以 n ! n! n!), d p [ i ] [ j ] ( j < n ) dp[i][j](j<n) dp[i][j](j<n)的目的只是转移到下一个状态.

不超时体会:把所有的判别函数都存进数组。

代码:

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<map>
using namespace std;
typedef long long ll;
const int length = 6000;
int reco[length];
const int len = 1e6 + 5;
vector<int> kl;
int cnt[len];
int ksm(int x, int t, int p)
{
	int ans = 1;
	while (t)
	{
		if (t % 2 == 1)
		{
			ans = 1ll * ans*x%p;
		}
		t = t >> 1;
		x = 1ll * x*x%p;
	}
	return ans;
}
int chk[4] = { 2,3,5,7 };
int check(int x)
{
	if (x < 10)
	{
		for (int i = 0; i < 4; i++)
		{
			if (x == chk[i])
			{
				return 1;
			}
		}
		return 0;
	}
	int p = x;
	int k = 0;
	x--;
	while (x%2==0)
	{
		x = x >> 1;
		k++;
	}
	for (int i = 0; i < 4; i++)
	{
		int a = ksm(chk[i], x, p);
		for (int j = 0; j < k; j++)
		{
			int tmp = 1ll * a*a%p;
			if (tmp == 1 && a != 1 && a!=p - 1)
				return 0;
			a = tmp;
		}
		if (a != 1)
			return 0;
	}
	return 1;
}
int fact[length];
int dp[length][length];
map<int,int> rev;
int mod = 998244353;
int rev1(int x)
{
	return ksm(x, mod - 2, mod);
}
int div1(int a, int b)
{
	return 1ll * a*rev[b] % mod;
}
int prime[len];
int main(void)
{
	int n;
	scanf_s("%d", &n);
	int max_reco = -1;
	for (int i = 0; i < 2*n; i++)
	{
		scanf_s("%d", &reco[i]);
		cnt[reco[i]]++;
		max_reco = max(max_reco, reco[i]);
	}
	fact[0] = 1;
	for (int i = 1; i <= 2 * n; i++)
	{
		fact[i] = 1ll * fact[i - 1] * i%mod;
	}
	dp[0][0] = 1;
	//kl.insert(kl.begin(), 0);
	kl.push_back(0);
	for (int i = 1; i <= max_reco; i++)
	{
		if (cnt[i])
		{
			kl.push_back(i);
			rev[fact[cnt[i]]] = rev1(fact[cnt[i]]);
			rev[fact[cnt[i] - 1]] = rev1(fact[cnt[i]-1]);
			prime[i] = check(i);
		}
	}
	for (int i = 1; i < kl.size(); i++)
	{
		dp[i][0] = div1(dp[i - 1][0] , fact[cnt[kl[i]]]);
		for (int j = 1; j <= min(i, n); j++)
		{
			if (prime[kl[i]])
				dp[i][j] = ((ll)div1(dp[i - 1][j - 1] ,  fact[cnt[kl[i]]-1]) +
				           div1(dp[i - 1][j] , fact[cnt[kl[i]]]))%mod;
			else
				dp[i][j] = div1(dp[i - 1][j] , fact[cnt[kl[i]]]);
		}
	}
	int ans = 1ll * dp[kl.size() - 1][n] * fact[n]%mod;
	printf("%d", ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值