题意:
给定 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;
}