【计数DP】CF1794D

news/2024/7/15 17:21:02 标签: 算法, 图论, c++

Problem - D - Codeforces

题意

思路

解法大方向对了,但是还是不会做,原因是组合数不知道怎么求

首先需要注意到一些东西:

1.底数一定是质数

2.质数个数 < n 一定无解

3.哪些质数作为底数是不确定的

4.n <= 2022

那么我们其实可以把做法大致猜出来

根据第4点,应该是个二维的dp,且状态的设计感觉非常唯一:

设 dp[i][j] 表示前 i 个数,已经选了 j 个数作为底数的方案数

然后就是考虑转移,这种计数类的dp在转移的时候都是考虑多一格会多出多少贡献,贡献一般由组合数求出

问题就是出在这里,我不知道怎么求组合数,一心想着对于指数求可重集排列,但是这么多的指数的个数是很难维护的

其实应该考虑分配,算出组合就行了,不用去考虑排列

设多出来那一格的数为 x, 它的个数为 y

那就是考虑把这 y 个 x 分配到 指数中,指数中还剩余多少个空位呢

前缀已经选了 j 个底数,设前缀的个数和为 sum,那么前缀有 sum - j 个数作为指数

所以还剩下 n - (sum - j) 个位置,也就是说,在 n - (sum - j) 个位置中选 y 个位置,那就是 C(n - (sum - j), y)

如果作为底数也是同样的道理

对 x 作为指数还是底数讨论一下即可

Code:

#include <bits/stdc++.h>

#define int long long

constexpr int N = 1e6 + 10;
constexpr int mod = 998244353;
constexpr int Inf = 0x3f3f3f3f;

int n, x;
int len = 0;
int Fac[N];
int inv[N];
int vis[N], prime[N];

int qpow(int a, int b) {
	int res = 1;
	while(b) {
		if (b & 1) res = (res * a) % mod;
		a = (a * a) % mod;
		b >>= 1;
	}
	return res;
}
int C(int n, int m) {
	if (n < 0 || m < 0 || n < m) return 0;
	return Fac[n] * inv[m] % mod * inv[n - m] % mod;
}
void Fac_init() {
	Fac[0] = 1;
	for (int i = 1; i < N; i ++) {
		Fac[i] = (Fac[i - 1] * i) % mod;
	}
	inv[N - 1] = qpow(Fac[N - 1], mod - 2);
	for (int i = N - 2; i >= 0; i --) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	}
}
void P_init(int n) {
	vis[1] = 1;
	for (int i = 2; i <= n; i ++) {
		if (!vis[i]) prime[++len] = i;
		for (int j = 1; i <= n / prime[j]; j ++) {
			vis[i * prime[j]] = 1;
			if (i % prime[j] == 0) break;
		}
	}
}
void solve() {
	std::cin >> n;

	std::map<int, int> mp;
	for (int i = 1; i <= 2 * n; i ++) {
		std::cin >> x;
		mp[x] += 1;
	}

	int sum = 0;
	std::vector<int> dp(n + 1, 0);
	dp[0] = 1;
	for (auto [x, y] : mp) {
		std::vector<int> ndp(n + 1, 0);
		for (int j = 0; j <= n; j ++) {
			ndp[j] += dp[j] * C(n - (sum - j), y) % mod;
			ndp[j] %= mod;
			if (j >= 1 && !vis[x]) {
				ndp[j] += dp[j - 1] * C(n - (sum - j + 1), y - 1) % mod;
				ndp[j] %= mod;
			}
		}
		dp.swap(ndp);
		sum += y;
		sum %= mod;
	}
	
	std::cout << dp[n] % mod << "\n";
}
signed main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);

	int t = 1;
	Fac_init();
	P_init(1e6);
	while (t--) {
		solve();
	}
	return 0;
}


http://www.niftyadmin.cn/n/5119132.html

相关文章

Java枚举(Enum)的使用

目录 一、枚举类型的定义 二、枚举类型的使用 &#xff08;一&#xff09;、枚举类型的常用方法 &#xff08;二&#xff09;、枚举的简单使用 &#xff08;1&#xff09;、和switch的搭配使用 &#xff08;2&#xff09;、枚举类型的values方法 &#xff08;3&#xff…

C++数据结构X篇_21_插入排序(稳定的排序)

文章目录 1. 插入排序原理2. 算法图解3. 核心代码&#xff1a;4. 插入排序整体代码实现 1. 插入排序原理 插入排序是一种最简单直观的排序算法&#xff0c;它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相…

C算法:不使用第三变量,实现两数交换

写一个函数实现两数交换&#xff0c;要求不使用第三个变量。 输入样例&#xff1a; 14 16 输出样例&#xff1a; 16 14 代码实现&#xff1a; #include<stdio.h>int main() {int a,b;printf("please input two num:\n"); scanf("%d%d",&…

Java 中如何将一个类中方法的局部变量在另一个方法中调用?

需求背景&#xff1a; A方法调用B方法&#xff0c;再调到C方法&#xff0c;希望A方法的一个局部变量boolean a&#xff0c;可以在B方法和C方法传递和改变。希望在B方法改变这个变量的值&#xff0c;然后控制其值在C方法的作用。 &#xff08;1&#xff09;刚开始是下面这样实…

快速入门Elasticsearch:安装、基本概念、分词器和文档基本操作详解

本文主要介绍快速入门 Elasticsearch&#xff0c;从 安装 、 基本概念 、 分词器 、*** 文档基本操作 *** 这 4 个方面快速入门。 Elasticsearch 是一款近实时的搜索引擎&#xff0c;底层是基于 Lucene 做搜索&#xff0c;再此基础上加入了分布式的特性&#xff0c;以便支持海…

离线电商数仓(二)

数据调研 业务调研&#xff1a;熟悉业务流程&#xff0c;明确每个业务的具体流程&#xff0c;需要将该业务所包含的每个业务过程一一列举出来。将数据和业务过程中的变化过程了解清楚。需求分析&#xff1a;需要明确需求所需的业务过程及维度&#xff0c;例如该需求所需的业务…

少走弯路!彻底学会spring源码应用实战

简介 相信大家能经常性的遇到项目上各类excel的导出&#xff0c;简单的excel格式&#xff0c;用简单的poi&#xff0c;easyExcel等工具都能导出。但是针对复杂的excel&#xff0c;有固定的样式、合并单元格、动态列等各类要求&#xff0c;导致excel 导出需要花很大一部分精力去…

防关联浏览器推荐:MuLogin指纹浏览器安全登录多平台账号

在现今的数字时代&#xff0c;我们的生活离不开互联网。我们使用在线平台进行银行交易、购物、社交媒体互动和其他各种活动。为了保护个人隐私和账号安全&#xff0c;我们需要寻找一种安全且方便的方式来管理我们的在线账号。MuLogin指纹浏览器正是为了满足这些需求而设计的一款…