P3200 [HNOI2009] 有趣的数列 题解

P3200 [HNOI2009] 有趣的数列

感性地,我们认为在按照数值从小到大填数时每个时刻所填的奇数位的个数 x 不小于所填偶数位的个数 y。我们考虑如何证明这一点。

性质1:每一个偶数位上的数都要大于它前面所有的数。这一点应当是显然的。

性质2:每一个偶数位上的数都不小于它的下标。这点结合一下性质1,容易知道这个数的最小值就是它的下标了。

反证法。假设 x<y,则最大偶数位的下标是 2y。也就是说会有 2y1 个数会比它小。但是不包含本次填数,之前一共填过了 x+y1 个数,由于 x<y ,所以 x+y1<2y1也就是说当前没有填满12y1的区间。又由于我们的填数是单调的,因此无论下来什么时候填,该区间内定会有一个数大于当前所填的数,违反性质1,矛盾,假设不成立

那么这就是一个裸卡特兰数的问题了。由于模数不是质数,因此我们只能考虑暴力分解质因数来解决,因此所用的式子是

Ansi=(2nn)n+1

化简一下就是

(n+2)(n+3)(2n1)2nn!

暴力对这个式子的每一项分解质因数会 TLE 60pts,因此需要一些常用的小优化。

具体地,我们线性筛预处理 12n 之间的每个数并记录 prm[i] 表示这个数 i 是由哪个因数筛掉的,在最终统计答案时就可以直接while循环模拟递归跳每个数的质因数了。

复杂度?预处理是 O(n) 的,下面跳质数复杂度均摊应该不会超过 O(n log(n)),具体不太会证。有 O(n) 做法,懒得学了 qwq

代码:

#include <bits/stdc++.h>
#define N 2000005
#define int long long
using namespace std;
int n, mod;
vector<int>v;
int prm[N]; //每个数是被哪个质数筛掉 
void init() {
	for (int i = 2; i < N; i++) {
		if (!prm[i]) {
			v.push_back(i);
			prm[i] = i;
		}
		for (auto j : v) {
			if (i * j >= N)
				break;
			prm[i * j] = j;
		}
	}
}
int qpow(int x, int y) {
	int ans = 1;
	while (y) {
		if (y & 1)
			ans = ans * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return ans;
}
int cnt[N]; //每个质数出现个数 
signed main() {
	cin >> n >> mod;
	init();
	for (int i = n + 2; i <= 2 * n; i++) {
		int x = i;
		while (x > 1)
			cnt[prm[x]]++, x /= prm[x];
	}
	for (int i = 2; i <= n; i++) {
		int x = i;
		while (x > 1)
			cnt[prm[x]]--, x /= prm[x];
	}
	int ans = 1;
	for (int i = 1; i < N; i++)
		ans = ans * qpow(i, cnt[i]) % mod;
	cout << ans << "\n";
	return 0;
}
posted @   长安19路  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示