题意:输入n,K,求第K大的非法括号序列,若没有第K大,输出-1。题目保证运算的数据都在long long范围内。
解法:
1. 如果n为奇数,那么所有的序列都是非法的,令右括号为二进制1,左括号为二进制0,从高位往低位决定每一位是左括号还是右括号。其实输出的就是K的二进制表示。
2. 如果n为偶数,同上一题,还是从高位往低位决定前缀是什么,从而确定每一位是左括号还是右括号,只是这次在求一个前缀的大小时,需要先加上所有可能,再减去合法的括号序列个数。定义函数get(n,i, j, k),n为括号序列总长度,i为当前前缀的左括号个数,j为当前前缀的右括号个数,k为当前前缀是否合法。get返回当前前缀下,非法括号序列的个数。
get的实现:若当前前缀已经非法,直接返回所有可能的括号序列,即2^(n-i-j)。若当前前缀合法,用折线法求出当前前缀下,合法括号个数T=C(n-i-j,n/2-i)-C(n-i-j,n/2-i-1),组合数边界需要注意一下。则非法个数即为2^(n-i-j)-T。
3. 没有第K大的判断方式:若生成完括号序列之后,K还没有被减为0,便视为非法情况。
#include <bits/stdc++.h>
using namespace std;
const int maxn=55;
int q,n;
long long K,C[maxn][maxn];
inline void init() {
for (int i=0;i<maxn;++i)
C[i][0]=1;
for (int i=1;i<maxn;++i)
for (int j=1;j<=i;++j)
C[i][j]=C[i-1][j]+C[i-1][j-1];
}
inline void print1() {
--K;
string s;
for (int i=n-1;i>=0;--i)
if (K>=(1LL<<i)) {
K-=(1LL<<i);
s+=')';
} else
s+='(';
if (K)
cout<<-1<<endl;
else
cout<<s<<endl;
}
inline long long get(int l,int r,int f) {
long long ret=1LL<<(n-l-r);
if (f)
return ret;
if (n/2-l>=0)
ret-=C[n-l-r][n/2-l];
if (n/2-l>0)
ret+=C[n-l-r][n/2-l-1];
return ret;
}
inline void print2() {
string s;
int det=0,f=0,l=0,r=0;
for (int i=0;i<n;++i) {
long long temp=get(l+1,r,f);
if (temp>=K) {
++l;
++det;
s+='(';
} else {
++r;
K-=temp;
s+=')';
--det;
if (det<0)
f=1;
}
}
--K;
if (K)
cout<<-1<<endl;
else
cout<<s<<endl;
}
int main()
{
init();
scanf("%d",&q);
while (q--) {
scanf("%d%I64d",&n,&K);
if (n&1)
print1();
else
print2();
}
return 0;
}