括号序列字典序、第k小括号序列、下一个括号序列
认为左括号字典序小于右括号
\(n\) 表示序列总长而不是括号对数
合法括号序列计数(共 \(k\) 种不同括号):
\[\dfrac{\binom{n}{0.5n}}{0.5n+1}k^{0.5n} \]给定括号序列求他的字典序
要求出比 \(s\) 字典序小的合法括号序列个数,枚举 \(i\),表示对于 \(1\le j ,都有 \(p_j=s_j\),而对于 \(i\),有 \(p_i
其中 \(p\) 就是那个比 \(s\) 小的合法括号序列,同时可以知道这里 \(s_i\) 一定是右括号
设当前 \([1,i]\) 种左括号比右括号多了 \(k\) 个,那么 \([i+1,n]\) 中右括号就应该比左括号多了 \(k\) 个(准确说是有 \(k\) 个不匹配的,并且没有不匹配的左括号)
设 \(f(i,j)\) 表示长度为 \(i\) 的括号序列,没有不匹配的左括号,不匹配的右括号有 \(j\) 个的方案数
枚举序列第一个填什么,可以得到转移 \(f(i,j)=f(i-1,j-1)+f(i-1,j+1)\)
这样可以 \(O(n^2)\) 解决了
另外,如果有多种括号,方法类似,变成对于每个 \(s_i\) 考虑所欲比他小的字符进行计算(比如上面比右括号小的只有左括号,就只计算了左括号的情况)
inline void pre(int n){
f[0][0]=1;
for(int i=1;i<=n;i++){
f[i][0]=f[i-1][1];
for(int j=1;j<=n;j++) f[i][j]=f[i-1][j-1]+f[i-1][j+1];
}
}
inline BigInt rank(char *s){
int n=std::strlen(s+1);
pre(n);
BigInt ans;ans.clear();
int num=0;
for(int i=1;i<=n;i++){
if(s[i]==')') ans+=f[n-i][num+1],num--;
else num++;
}
return ans+1;
}
给定字典序求对应的括号序列
求字典序第 \(k\) 小的合法括号序列
还是从前往后考虑,看这个位置能不能填右括号,如果填了右括号根据上面的理论需要要求 \(k> f(n-i,num+1)\)
其中 \(num\) 是当前左括号比右括号多的数量,因为若填了右括号会产生 \(f(n-i,num+1)\) 个比它小的括号序列 \(p\)
如果不能填右括号,还满足填了左括号以后不会使得后面都填右括号也不能使得序列合法,也就是 \(num<\dfrac{n}{2}\),那么就填左括号
否则,填右括号,给 \(k\) 减去 \(f(n-i,num+1)\)
inline void kth(int n,BigInt k,char *s){
pre(n);
int num=0;
for(int i=1;i<=n;i++){
if(num+1<=(n>>1)&&f[n-i][num+1]>=k) s[i]='(',num++;
else{
s[i]=')';
if(num+1<=(n>>1)) k-=f[n-i][num+1];
num--;
}
}
}
对于有多种括号的情况,给出只有两种括号的代码,来自:https://cp-algorithms.com/combinatorics/bracket_sequences.html
string kth_balanced2(int n, int k) {
vector> d(2*n+1, vector(n+1, 0));
d[0][0] = 1;
for (int i = 1; i <= 2*n; i++) {
d[i][0] = d[i-1][1];
for (int j = 1; j < n; j++)
d[i][j] = d[i-1][j-1] + d[i-1][j+1];
d[i][n] = d[i-1][n-1];
}
string ans;
int shift, depth = 0;
stack st;
for (int i = 0; i < 2*n; i++) {
// '('
shift = ((2*n-i-1-depth-1) / 2);
if (shift >= 0 && depth + 1 <= n) {
int cnt = d[2*n-i-1][depth+1] << shift;
if (cnt >= k) {
ans += '(';
st.push('(');
depth++;
continue;
}
k -= cnt;
}
// ')'
shift = ((2*n-i-1-depth+1) / 2);
if (shift >= 0 && depth && st.top() == '(') {
int cnt = d[2*n-i-1][depth-1] << shift;
if (cnt >= k) {
ans += ')';
st.pop();
depth--;
continue;
}
k -= cnt;
}
// '['
shift = ((2*n-i-1-depth-1) / 2);
if (shift >= 0 && depth + 1 <= n) {
int cnt = d[2*n-i-1][depth+1] << shift;
if (cnt >= k) {
ans += '[';
st.push('[');
depth++;
continue;
}
k -= cnt;
}
// ']'
ans += ']';
st.pop();
depth--;
}
return ans;
}
求字典序的下一个括号序列
找到一个最大的 \(i\),满足:
- \(s_i\) 是左括号
- \([1,i-1]\) 种左括号的数量 严格大于 右括号的数量
然后将 \(s_i\) 变成右括号,并重构序列 \([i+1,n]\) 的部分
若改变 \(s_i\) 后,\([1,i]\) 种左括号比右括号多了 \(k\) 个,为了让后面的字典序尽可能小,让最后 \(k\) 个都是右括号,而剩下的就用 \(((\cdots (())\cdots ))\) 的形式填充即可
复杂度 \(O(n)\)