题意:
给定
n
,
k
n,k
n,k,问第
k
k
k小的合法括号序列是什么。
你可以将左括号看成
0
0
0,右括号看成
1
1
1,求字典序第
k
k
k小的括号序列。
数据范围:
1
≤
n
≤
2000
,
1
≤
k
≤
1
0
18
1\leq n\leq 2000,1\leq k\leq 10^{18}
1≤n≤2000,1≤k≤1018
题解:
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j]为长度为
i
i
i的左括号个数减右括号个数为
j
j
j的合法括号序列方案数,保证左括号个数不小于右括号个数。
那么状态转移方程为:
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
+
1
]
+
f
[
i
−
1
]
[
j
−
1
]
f[i][j] = f[i-1][j+1]+f[i-1][j-1]
f[i][j]=f[i−1][j+1]+f[i−1][j−1],注意
f
[
i
]
[
j
]
f[i][j]
f[i][j]只需最多到
k
k
k即可。
之后从前往后枚举每个位置应该放置的括号。
当前枚举到位置
i
i
i,左括号个数减右括号个数为
n
o
w
now
now。
先看是否可以放置左括号,条件为放置左括号的总方案数不比
k
k
k小,
即
f
[
n
−
i
]
[
n
o
w
+
1
]
≥
k
f[n-i][now+1]\geq k
f[n−i][now+1]≥k,则放置左括号即可,
n
o
w
now
now加
1
1
1。
否则放置右括号,需要减去该位置放置左括号的所有方案
f
[
n
−
1
]
[
n
o
w
+
1
]
f[n-1][now+1]
f[n−1][now+1],
n
o
w
now
now减
1
1
1。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2010;
const ll MAX = 1e18;
ll f[N][N]; //f[i][j]长为i,j为cnt0 - cnt1的方案数
char ans[N];
int n;
ll k;
int main()
{
scanf("%d%lld", &n, &k);
memset(f, 0, sizeof f);
f[0][0] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= i; ++j) {
//1. i - 1, j + 1
if(i - 1 >= j + 1) f[i][j] += f[i - 1][j + 1];
//2. i - 1, j - 1
if(j - 1 >= 0) f[i][j] += f[i - 1][j - 1];
f[i][j] = min(f[i][j], MAX);
}
int now = 0;
for(int i = 1; i <= n; ++i) {
//由于左括号越靠前则该括号序列的值越小,故优先看能不能放左括号
//如果该位置放置左括号的方案小于k,就需要放置右括号,并且把左括号的方案删除。
if(f[n - i][now + 1] >= k) ans[i] = '(', ++now;
else ans[i] = ')', k -= f[n - i][now + 1], --now;
}
ans[n + 1] = '\0';
puts(ans + 1);
return 0;
}