CSES Bracket Sequences I 題解
題目
- Time limit: 1.00 s
- Memory limit: 512 MB
Your task is to calculate the number of valid bracket sequences of length . For example, when , there are sequences:
- ()()()
- ()(())
- (())()
- ((()))
- (()())
| Input | Output |
|---|---|
| The only input line has an integer . | Print the number of sequences modulo . |
Constraints
Example:
| Input | Output |
|---|---|
| 6 | 5 |
解法
這題很顯然是在考卡特蘭數
卡特蘭數
定義
卡特蘭數表示有A, B各個,A永不落後B的排列方法數。組合學上有很多問題都跟卡特蘭數有關。
公式
我們可以將卡特蘭數問題轉換成網格上,從左下到右上角的路徑方法數,最終可推得
(網路上有很多教學,在此就不贅述了,反正推過你最後還是得把結果記起來)
遞迴
卡特蘭數的遞迴式如下:
原題分析
顯然為奇數時答案為,而為偶數時所求即。
接著我們只需想辦法算出即可。而
我們令(代表分子,numerator)、(代表分母,denominator),則
接著兩邊同時mod ,假設模完他變成,那麼
所以我們還需要寫一個用來求模反元素的函式。
求解模反元素
若(此時才必有唯一的模反元素),則稱
的解為模的反元素,記作
方法一:費馬小定理+快速冪
由於模數為質數,由費馬小定理知
而可由上次提過的快速冪算出。
實現程式碼如下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll fast_pow(ll base, ll exp, int m){
ll res = 1LL;
while(exp > 0){
if(exp%2==1){res = (res*base) % m;}
base = (base*base) % m;
exp /= 2;
}
return res;
}
ll invert(ll a, ll M){ // 僅當M為質數時可用
return fast_pow(a, M-2, M)
}
當然,就算不是質數,我們還是可以用歐拉定理推出
但這等於我們還要先去算出歐拉函數,通常不會比較方便。因此此時較為建議以下方法二。
方法二:擴展歐幾里得算法
打CTF也打多久了,怎麼連個擴展歐幾里得都不會寫
我也是搞好久才懂
這個演算法十分重要,打Crypto時也會遇到(騙你的,這太基礎了題目大概是不會考這個w)
我們都知道歐幾里得演算法(也就是輾轉相除法)可以用來求兩數的最大公因數,那擴展歐幾里得算法是拿來幹嘛的呢?
貝祖定理告訴我們,對於所有且,存在使得
擴展歐幾里得演算法就是用來找出這組整數的。
以下說明用到了遞迴的概念。
先將以除,得到
由輾轉相除法原理,,令為。
現在我們想像已寫成的線性組合(貝祖定理)。
ㄟ,那不就把代入再移項,就能整理成的樣子了嗎?(注意,是已知數,為)如下
所以我們只要知道,就能知道,其中
那我們怎麼求出呢?沒錯,用擴展歐幾里得演算法。(所以說是遞迴嘛)
欲遞迴,我們記得處理base case,函式才停得下來。最簡單的base case即當,返回。
(因為輾轉相除法會一直做到整除,也就是最後一個為止)
以下程式:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
tuple<ll, ll, ll> exgcd(ll a, ll b){
if(b==0) return {a, 1, 0};
auto [gcd, x1, y1] = exgcd(b, a%b);
ll x = y1;
ll y = x1 - (a/b)*y1;
return {gcd, x, y};
}
好,但這跟我們求模反元素有什麼關係呢?請看以下:
因為互質,我們可以用擴展歐幾里得演算法求出使得
兩邊同時模,瞬間得到即我們要找的模反元素。
ll invert(ll a, ll m){
auto [gcd, x, y] = exgcd(a, m);
if (gcd != 1) throw invalid_argument("Does not exist."); // a, m互質才有模反元素
return (x%m + m)%m;
}
AC Code
好耶!我們將以上全部組合起來,得到以下:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int M = 1e9 + 7;
tuple<ll, ll, ll> exgcd(ll a, ll b){
if(b==0) return {a, 1, 0};
auto [gcd, x1, y1] = exgcd(b, a%b);
ll x = y1;
ll y = x1 - (a/b)*y1;
return {gcd, x, y};
}
ll invert(ll a, ll m){
auto [gcd, x, y] = exgcd(a, m);
if (gcd != 1) throw invalid_argument("Does not exist.");
return (x%m + m)%m;
}
int main(){
ios::sync_with_stdio(0), cin.tie(0); // IO加速
int n;
cin >> n;
if(n % 2 == 1){cout << 0; return 0;}
n /= 2;
ll den=1; // 分母
ll num=1; // 分子
for(int i=1;i<=n;i++) den = (den*i)%M;
for(int i=n+2;i<=2*n;i++) num = (num*i)%M;
cout << invert(den, M)*num % M;
}
AC!收工!



![[Note] Writing a simple Program in C - LiveOverflow](/2026/06/03/Note-Writing-a-simple-Program-in-C-LiveOverflow/output.png)