P3200 [HNOI2009] 有趣的数列

发布时间 2023-09-14 14:35:15作者: FOX_konata

原题

这题我\(O(n^2)\)的做法竟然没有想出来,反思QwQ

首先\(O(n^2)\)的做法很好想,我们考虑从小到大往数组里填数,显然我们要求任何时刻编号为奇数的位置要填的比编号为偶数的位置要不少才行

于是我们设\(dp_{i,j,k}\)表示填了前\(i\)个数,奇数位填的个数为\(j\),偶数位填的个数为\(k\)。首先我们先不说转移,可以发现\(k = i - j\),于是我们把\(k\)这一位省掉

于是我们有转移:

\[dp_{i,j} = \begin{cases} dp_{i-1,j}+dp_{i-1,j-1} & (j>k) \\ dp_{i-1,j} & (j = k) \end{cases} \]

于是我们拿到了\(50pts\)


我们要怎么发现这个东西是\(catalan\)数呢?我们看这个限制:

任何时刻编号为奇数的位置要填的比编号为偶数的位置要不少

我们可以发现catalan数就是用来求这种限制的。因为我们考虑一种思考方式:我们从\((0,0) \rightarrow (2n,0)\),如果是放到奇数位就向右上走,放到偶数位向右下走,不得碰到\(x\)轴以下的方案数,这个很明显是catalan数,因此最终答案就是第\(n\)个catalan数

完结撒花……

我们发现我们开香槟开早了,因为\(p\)不是一个质数,我们不太好计算答案

我们发现catalan数有\(\large{ C_n = \frac{\binom{2n}{n}}{n+1} = \frac{\prod_{i=n+2}^{2n}{i}}{n!} }\),因此我们可以用素数筛出前\(2n\)个数的最小质因子,并把所有数质因数分解后再计算,这么做是\(O(n \log n)\)

但其实我们可以优化一下,就是开一个桶记录某一个质因子的个数,每次向最小质因子和剩下的部分转移,可以做到\(O(n)\)(这里我知道我表达的很烂,因此给出代码)

#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define pcn putchar('\n')
#define ll long long
#define LL __int128
#define MP make_pair
#define fi first
#define se second
#define gsize(x) ((int)(x).size())
#define Min(a, b) (a = min(a, b))
#define Max(a, b) (a = max(a, b))
#define For(i, j, k) for(int i = (j), END##i = (k); i <= END##i; ++ i)
#define For__(i, j, k) for(int i = (j), END##i = (k); i >= END##i; -- i)
#define Fore(i, j, k) for(int i = (j); i; i = (k))
//#define random(l, r) ((ll)(rnd() % (r - l + 1)) + l)
using namespace std;

namespace IO {
	template <typename T> T read(T &num){
	    num = 0; T f = 1; char c = ' '; while(c < 48 || c > 57) if((c = getchar()) == '-') f = -1;
	    while(c >= 48 && c <= 57) num = (num << 1) + (num << 3) + (c ^ 48), c = getchar();
	    return num *= f;
	}
	ll read(){
	    ll num = 0, f = 1; char c = ' '; while(c < 48 || c > 57) if((c = getchar()) == '-') f = -1;
	    while(c >= 48 && c <= 57) num = (num << 1) + (num << 3) + (c ^ 48), c = getchar();
	    return num * f;
	}
	template <typename T> void Write(T x){
	    if(x < 0) putchar('-'), x = -x; if(x == 0){putchar('0'); return ;}
	    if(x > 9) Write(x / 10); putchar('0' + x % 10); return ;
	}
	void putc(string s){ int len = s.size() - 1; For(i, 0, len) putchar(s[ i ]); }
	template <typename T> void write(T x, string s = "\0"){ Write( x ), putc( s ); }
}
using namespace IO;

/* ====================================== */

const int maxn = 2e6 + 50;

int n;
ll mod;
int prime[ maxn >> 3 ], cntp;
int minp[ maxn ];
int cnt[ maxn ];

void GetPrime(int n){
	For(i, 2, n){
		if(!minp[ i ]){
			prime[ ++ cntp ] = i;
			minp[ i ] = i;
		}
		For(j, 1, cntp){
			static int x; x = i * prime[ j ];
			if(x > n) break;
			minp[ x ] = prime[ j ];
			if(i % prime[ j ] == 0) break;
		}
	}
}

ll power(ll a, ll b){
	ll ans = 1;
	while(b){
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

void mian(){
	read(n), read(mod);
	GetPrime(n << 1);
	For(i, 2, n) cnt[ i ] = -1; // 作为除法
	For(i, n + 2, n << 1) cnt[ i ] = 1; // 作为乘法
	For__(i, n << 1, 2){
		if(minp[ i ] < i){ // 向最小质因子和剩下部分转移
			cnt[ minp[ i ] ] += cnt[ i ];
			cnt[ i / minp[ i ] ] += cnt[ i ];
			cnt[ i ] = 0;
		}
	}
	ll ans = 1;
	For(i, 2, n << 1){
		if(minp[ i ] == i){
			ans = ans * power(i, cnt[ i ]) % mod; // 计算答案
		}
	}
	write(ans, "\n"); 
}

void init(){

}

int main() {

#ifdef ONLINE_JUDGE
#else
	freopen("data.in", "r", stdin);
//	freopen("data.out", "w", stdout);
#endif

	int T = 1;
//	read(T);
	while(T --){
		init();
		mian();
	}

	return 0;
}