AcWing 130. 火车进出栈问题(卡特兰数、高精度、分解质因数)

题目链接:点击这里
在这里插入图片描述

%yxc

n n 个数,就需要进栈 n n 次,出栈 n n 次,共 2 n 2n 次操作

为了描述方便,我们定义符号 + + 为进栈,符号 - 为出栈。那么,不同的合法的车厢出站序列对应着不同的 + +- 序列,

n = 3 n=3 为例:

+ + +    3   2   1 +++---\ \ 3\ 2\ 1

+ + +    2   3   1 ++-+--\ \ 2\ 3\ 1

+ + +    2   1   3 ++--+-\ \ 2\ 1\ 3

+ + +    1   2   3 +-+-+-\ \ 1\ 2\ 3

+ + +    1   3   2 +-++--\ \ 1\ 3\ 2

于是,要求车厢出栈的可能排列方式有多少种,问题就转换为, n n + + n n - 能组成多少个合法的序列。

这里说的合法是有含义的,例如: + + + ++--+- 合法、 + +-- 不合法。

  1. + + 肯定是合法的
  2. - 一定要保证当前栈非空,也就是,对每个 - 来说,任何前缀中的 s u m ( ) s u m ( + ) sum(-) \leq sum(+)
    满足这一核心的就是卡特兰数。

n n + + n n - 组成的总方案数,等价于从 2 n 2n 个位置中选出 n n + + 的方案数,即 C 2 n n C_{2n}^{n}

易知, C 2 n n C_{2n}^{n} - 不合法方案数 = 合法方案数

下面用非降路径来求解不合法方案数:

首先定义 + + 为向右走, - 为向上走。

那么,合法路径的特点就是:任意时刻向右走的距离一定大于等于向上走的距离,即 y x y \leq x ,整个路径一定在直线 y = x y=x 的下方,由于有 n n + + n n - ,其终点一定在 ( n , n ) (n,n)

非法路径的特点: y > x y>x ,即 y x + 1 y \geq x+1 ,其终点也在 ( n , n ) (n,n)

如果将非法路径与直线 y = x + 1 y=x+1 第一个交点后面的部分关于直线 y = x + 1 y=x+1 轴对称,可以得到一个结论:任何一条非法路径都对应着一条从 ( 0 , 0 ) (0,0) ( n 1 , n + 1 ) (n-1,n+1) 的路径。

这样一转换,非法路径数量就很好求了:没有限制地从 ( 0 , 0 ) (0,0) ( n 1 , n + 1 ) (n-1,n+1) 的路径数,等价于 n 1 n-1 + + n + 1 n+1 - 组成的方案数,等价于 从 2 n 2n 个位置中选出 n 1 n-1 + + 的方案数,即 C 2 n n 1 C_{2n}^{n-1}

最终合法的答案就是 C 2 n n C 2 n n 1 C_{2n}^{n} - C_{2n}^{n-1} ,代入公式展开,推导结果为 C 2 n n n + 1 \frac{C_{2n}^{n}}{n+1}

数据 1 n 60000 1≤n≤60000 过大,考虑高精度,超时。(看着y总debug也学到了不少)

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>

using namespace std;

void multi(vector<int> &a, int b)		//高精度乘 
{
	int t = 0;
	for(int i = 0; i < a.size(); i++)
	{
		a[i] = a[i] * b + t;
		t = a[i] / 10;
		a[i] %= 10;
	}
	while(t)
	{
		a.push_back(t%10);
		t /= 10;
	}
}

void div(vector<int> &a, int b)			//高精度除 
{
	int t = 0;
	for(int i = a.size() - 1; i >= 0; i--)			//倒序除 
	{
		a[i] += t*10;
		t = a[i] % b;
		a[i] /= b;
	}
	while(a.size() > 1 && a.back() == 0)		//删掉首位零
		a.pop_back();
}

void out(vector<int> &a)		//out方便debug
{
	for(int i = a.size() - 1; i >= 0; i--)
		printf("%d", a[i]);
	printf("\n");
}

int main()
{
	int n;
	scanf("%d", &n);
	
	vector<int> res;
	res.push_back(1);
	for(int i = 2 * n, j = 1; j <= n; i--, j++)
	{
		multi(res, i);
		div(res, j);
	}
	div(res, n+1);
	
	out(res);
    return 0;
}

vector换数组、高精度压9位,超时。

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>

using namespace std;

typedef long long ll;

const int N = 6000010;

ll res[N], tt; 

void multi(int b)		//高精度乘 
{
	ll t = 0;
	for(int i = 0; i <= tt; i++)
	{
		res[i] = res[i] * b + t;
		t = res[i] / 1000000000;
		res[i] %= 1000000000;
	}
	while(t)
	{
		res[++tt] = t % 1000000000; 
		t /= 1000000000;
	}
}

void div(int b)			//高精度除 
{
	ll t = 0;
	for(int i = tt; i >= 0; i--)			//倒序除 
	{
		res[i] += t*1000000000;
		t = res[i] % b;
		res[i] /= b;
	}
	while(!res[tt])			//删掉首位零
		tt--;
}

void out()				//out方便debug
{
	printf("%lld", res[tt]);
	for(int i = tt - 1; i >= 0; i--)
		printf("%09lld", res[i]);
	printf("\n");
}

int main()
{
	int n;
	scanf("%d", &n);
	tt = 0;
	res[0] = 1;
	for(int i = 2 * n, j = 1; j <= n; i--, j++)
	{
		multi(i);
		div(j);
	}
	div(n+1);
	
	out();
    return 0;
}

组合数 C 2 n n = ( 2 n ) ! n ! n ! C_{2n}^{n} = \frac{(2n)!}{n!*n!} ,对分子分母分解质因数,例如 C 6 3 = 6 5 4 3 2 1 = 2 2 3 0 5 1 C_{6}^{3} = \frac{6*5*4}{3*2*1} = 2^2*3^0*5^1

一个小技巧, n ! n! 里有多少个因子 2 2 n 2 \lfloor \frac{n}{2} \rfloor + n 2 2 \lfloor \frac{n}{2^2} \rfloor + n 2 3 \lfloor \frac{n}{2^3} \rfloor + … …,直到 2 k 2^k 大于 n n

AC代码:

#include <iostream>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 6000010, M = 120010;

LL res[N], tt;
int q[M];
bool st[M];

void multi(int b)			//高精度乘
{
    LL t = 0;
    for (int i = 0; i <= tt; i ++ )
    {
        res[i] = res[i] * b + t;
        t = res[i] / 1000000000;
        res[i] %= 1000000000;
    }
    while (t)
    {
        res[++tt] = t % 1000000000;
        t /= 1000000000;
    }
}

void out()			//输出 
{
    printf("%lld", res[tt]);
    for (int i = tt - 1; i >= 0; i -- ) printf("%09lld", res[i]);
    cout << endl;
}

int get(int n, int p)		//求n!中有多少个因子p 
{
    int s = 0;
    while (n) s += n / p, n /= p;
    return s;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 2; i <= 2 * n; i++)				//筛出2n以内的素数 
        for(int j = i + i; j <= 2 * n; j += i)
            st[j] = true;

    for(int i = 2; i <= n * 2; i++)
    {
    	if(!st[i])		//若i是素数,求其个数 
        {
            q[i] = get(n * 2, i) - get(n, i) * 2;
        }
	}

    int k = n + 1;					//还要减去分母n+1对应的因子数 
    for(int i = 2; i <= k; i++)
    {
    	while(k % i == 0)
        {
            k /= i;
            q[i] -- ;
        }
	}

    res[0] = 1;

    for(int i = 2; i <= n * 2; i++)
        while(q[i]--)
            multi(i);

    out();

    return 0;
}
发布了727 篇原创文章 · 获赞 111 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_42815188/article/details/104286409
今日推荐