火车进出栈问题

时间限制: 1 Sec  内存限制: 128 MB

题目描述

一列火车n节车厢,依次编号为1,2,3,…,n。每节车厢有两种运动方式,进栈与出栈,问n节车厢出栈的可能排列方式有多少种。

 

输入

一个数,n(n<=60000)

 

输出

一个数s表示n节车厢出栈的可能排列方式

 

样例输入

复制样例数据

3

样例输出

5

这道题目很烦,做对这道题目需要知道:

1.卡特兰数f的公式:

2.质因数分解。(几千位数,你一个个大数相乘就是在作死)

3.大数相乘。

一个整数,肯定是由多个素数组成,统计素数个数,然后用快速幂求各种素数乘积,最后每种素数再乘起来,就有了这个看似不会TL代码但TL了。哈哈。。。

/**/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <stack>
#include <queue>

typedef long long LL;
using namespace std;

int n;
int f[60005][205];
int prim[11305], vis[120005], tot, num[120005], v[100005];

void get_prim(){
	vis[1] = 1;
	for (int i = 2; i <= 2 * n; i++){
		if(!vis[i]) prim[tot++] = i;
		for (int j = 0; j < tot && prim[j] * i <= 2 * n; j++){
			vis[prim[j] * i] = 1;
			if(i % prim[j] == 0) break;
		}
	}
}

void mul(int a[], int &num1, int b[], int &num2){
	int c[100005];
	memset(c, 0, sizeof(c));
	for (int i = 0; i < num1; i++){
		for (int j = 0; j < num2; j++){
			c[i + j] += a[i] * b[j];
			if(c[i + j] > 9) c[i + j + 1] += c[i + j] / 10, c[i + j] %= 10;
		}
	}
	int len = num1 + num2;
	while(c[len - 1] == 0) len--;
	num1 = len;
	for (int i = 0; i < len; i++) a[i] = c[i];
}

void pow_prim(int x[], int &cnt, int num){
	int res[100005], len = 1;
	//memset(res, 0, sizeof(res));
	res[0] = 1;
	while(num){
		if(num & 1) mul(res, len, x, cnt);
		mul(x, cnt, x, cnt);
		num >>= 1;
	}
	cnt = len;
	for (int i = 0; i < cnt; i++) x[i] = res[i];
}

int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);

	scanf("%d", &n);
	if(n == 1 || n == 0){
		printf("1\n");
		return 0;
	}
	get_prim();
	for (int i = 2; i <= 2 * n; i++){
		if(i == n + 1) continue;
		if(i <= n){

			if(!vis[i]) num[i]--;
			else{
				int t = sqrt((double) i);
				int temp = i;
				for (int j = 2; j <= t && j <= temp; j++){
					if(vis[j]) continue;
					while(temp % j == 0 && temp) num[j]--, temp /= j;
					if(!vis[temp] && temp) num[temp]--, temp /= temp;
				}
			}
		}else{
			if(!vis[i]) num[i]++;
			else{
				int t = sqrt((double) i);
				int temp = i;
				for (int j = 2; j <= t && j <= temp; j++){
					if(vis[j]) continue;
					while(temp % j == 0 && temp) num[j]++, temp /= j;
					if(!vis[temp] && temp) num[temp]++, temp /= temp;
				}
			}
		}
	}
	int len = 1;
	v[0] = 1;
	for (int i = 0; i < tot; i++){
		if(!num[prim[i]]) continue;
		int t = prim[i];
		int w[100005], cnt = 0;
		while(t){
			w[cnt++] = t % 10;
			t /= 10;
		}
		pow_prim(w, cnt, num[prim[i]]);
		mul(v, len, w, cnt);
	}
	for (int i = len - 1; i >= 0; i--) printf("%d", v[i]);
	printf("\n");

	return 0;
}
/**/

 

然后我在想,你即使用快速幂+大数相乘,但是你大数相乘为O(n^2)算法,是吃不消的。然后想到FFT,然后各种找资料,硬生生的没看懂,可能自己太菜了,(潜意识觉得O(nlogn)也过不了),然后我在想怎么把大数乘法降到O(n),然后就想到一个int或者long long类型的数乘大数不就可以了吗,然后我担心超时,就直接弄了long long,降低大数相乘次数,虽然常数大,但是总比n^2快吧。。。然后就莫名其妙的过了。。。

/**/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <stack>
#include <queue>

typedef long long LL;
using namespace std;

int n, tot, prim[66060], vis[120005], num[120005];
LL ans[50005];

void get_prim(){
	for (int i = 2; i <= 2 * n; i++){
		if(!vis[i]) prim[tot++] = i;
		for (int j = 0; j < tot && prim[j] * i <= 2 * n; j++){
			vis[prim[j] * i] = 1;
			if(i % prim[j] == 0) break;
		}
	}
}

void mul(LL a[], LL num){
	int len = a[0];
	LL b[50005];
	for (int i = 1; i <= 50000; i++) b[i] = 0;
	for (int i = 1; i <= len; i++){
		b[i] += a[i] * num;
		if(b[i] > 9){
			b[i + 1] += b[i] / 10;
			b[i] %= 10;
		}
	}
	while(b[len + 1]){
		len++;
		if(b[len] > 9){
			b[len + 1] += b[len] / 10;
			b[len] %= 10;
		}
	}
	a[0] = len;
	for (int i = 1; i <= len; i++) a[i] = b[i];
}

LL poww(LL x, int num){
	LL res = 1;
	while(num){
		if(num & 1) res = res * x;
		x *= x;
		num >>= 1;
	}
	return res;
}

int main()
{
	//freopen("in.txt", "r", stdin);
	//freopen("out.txt", "w", stdout);

	scanf("%d", &n);
	get_prim();//先求出2*n内的素数
	for (int i = 2; i <= 2 * n; i++){//然后统计每种素数的个数
		if(i == n + 1) continue;
		if(i <= n){
			if(!vis[i]){
				num[i]--; continue;
			}
			int t = sqrt((double) i), tt = i;
			for (int j = 2; j <= t && j <= tt; j++){
				if(tt % j != 0) continue;
				while(tt % j == 0 && !vis[j]) num[j]--, tt /= j;
				if(!vis[tt]) num[tt]--, tt /= tt;
			}
		}else{
			if(!vis[i]){
				num[i]++; continue;
			}
			int t = sqrt((double) i), tt = i;
			for (int j = 2; j <= t && j <= tt; j++){
				if(tt % j != 0) continue;
				while(tt % j == 0 && !vis[j]) num[j]++, tt /= j;
				if(!vis[tt]) num[tt]++, tt /= tt;
			}
		}
	}
	ans[0] = 1, ans[1] = 1;
	for (int i = 0; i < tot; i++){
		if(!num[prim[i]]) continue;
		//cout << prim[i] << " " << num[prim[i]] << endl;
		LL t = (1LL << 59) / prim[i];//以t个prim[i]为一组
		LL tim = (LL)num[prim[i]] / t;
		for (int i = 1; i <= tim; i++){
			mul(ans, poww(prim[i], t));//用快速幂求出t个prim[i];
		}
		t = (LL)num[prim[i]] % t;//最后还剩下多少个prim[i]
		mul(ans, poww(prim[i], t));
	}
	for (int i = ans[0]; i >= 1; i--) printf("%lld", ans[i]);
	printf("\n");

	return 0;
}
/**/