又是久违的 tourist round。。。
Table of Contents
Problem D.
Problem F – Bracket Insertion
题意
构造一个初始为空的括号序列,执行 n 次插入操作,每次操作任选序列中的一个位置,插入 "()" 或 ")("。
问最终构成的括号序列为合法括号序列的操作方案的概率。
题解
组合计数。
先转换成计数问题,不难发现,对于第 i 次操作,我们有 i*2-1 种插入的位置可供选择,因而求出合法的操作方案总数,处以 1*3*5*...*2n-1 即可。
麻烦的是,我们甚至不能把每次插入的一对括号看成是一个独立的元素,后续插入操作甚至可以从中间破坏它。
回忆 Catalan 序列的做法,虽然和这个题看起来有很大的不同,前者是计数合法的括号序列总数,本题是计数合法的操作方案。
但是依然可以给我们提供许多有益的思路。回忆 Catalan 序列的卷积形递推公式。
我们实际上是考察第一组匹配的括号,然后分别枚举括号内或括号外的括号层数,以进行递归。
这个题是否可以使用同样的策略呢?
我们同样固定第一组括号,那么之后的括号,只会是下述三种情况之一:L(M)R。
因为被初始括号所分割,这里 L M R 的状态都独立的。
这里 L 和 R 的 size 可以合并在一起,这样我们只要枚举 M 的 size 即可。
这样我们就有了类 Catalan 的递推关系,进一步这里的不同时,子状态中并不一定要求是合法的括号序列,
我们需要记录子状态的不合法的等级,也就是前缀和的最小值。
进一步考虑细节,在转移时,我们发现我们只需要考虑前缀和 >= j 的状态,因此可以直接将前缀和定义在状态中。
f[i][j]: 执行了 i 次插入操作,且当前前缀和最小值不小于 -j 的方案数。
我们枚举左右的子树 size 和 a 或中间的子树 size b,得到转移方程:
![]()
这里的转移出现了组合数,一个常见的技巧是,我们可以类比 EFG 中的处理方法,对每个状态 f[i][j] 都除以 i! 以简化转移。(然后下标里有 -1 也很烦,不如也 offset 一下。。。)
https://codeforces.com/contest/1782/submission/189455837
#include <lastweapon/number>
using namespace lastweapon;
const int N = int(5e2) + 9;
Int f[N][N], f2[N][N], I[2*N], Fact[2*N]; // i step, min pref sum >= -(j-1)
int n; Int p, q;
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
//freopen("/Users/minakokojima/Documents/GitHub/ACM-Training/Workspace/out.txt", "w", stdout);
#endif
MOD = 998244353;
RD(n); p = Int(RD()) / 10000; q = Int(1) - p;
for (int i=(I[1]=1)+1; i<=2*n; i++) I[i] = 1ll * (MOD - MOD / i) * I[MOD%i] % MOD;
Fact[0] = 1; REP_1(i, n) Fact[i] = Fact[i-1] * i;
REP_1(i, n+1) f2[0][i] = f[0][i] = 1;
// \sum f[a1] * f[a2] * (p*f[b]+q*f[b]) * \binom{i-1}{a, b}
REP_1(i, n) REP_1(j, n-i+1) {
REP(a, i) {
int b = i-1-a;
f[i][j] += f2[a][j] * (p*f[b][j+1] + q*f[b][j-1]);
}
f[i][j] *= I[i];
REP(a, i+1) {
int b = i-a;
f2[i][j] += f[a][j] * f[b][j];
}
}
Int z = f[n][1] * Fact[n]; REP_1(i, n) {
z *= I[i*2-1];
}
cout << z << endl;
}
进一步,和 Catalan 序列一样,这个题也可以从树的角度进行思考。
我们设法建立起括号序列与一个有标号森林的一一对应关系(森林也可视为有一个虚根节点的树),
每次操作,我们都相当于按照顺序,插入一个新的有标号的叶子节点(区分孩子的顺序)(祖先的标号一定小于孩子)。
括号的状态对应节点处权值 (1 或 -1),括号序列的前缀和对应树中的一段 dfs 序,也就是根到每一个节点的路径的权值和。
括号序列合法,就是所有这些和都 >= 0。
同样的,在转移过程中,括号序列允许不合法,我们进行同样的加维,只是这里因为我们已经建立了一一对应关系,转移时我们可以直接对这里的树结构进行计数。
我们直接枚举树的形态,再用组合数重新进行标号,得到更简单的转移方程:
![]()
注意这里的 b+1 是因为,我们已经把 size 为 b 的森林,和一个新的节点作为了一个完整的子树,连接在虚根节点上。
得到的方程更为简洁。
(当然这两种做法是完全等价的,无论从括号序列的角度分析还是从树的角度分析,只是描述的语言略有不同罢了,都能得到两种转移方程,
区别是我们递归的时候是拎出来考察的是编号为 1 的节点所在的子树,还是位置靠边的一个子树。)
https://codeforces.com/contest/1782/submission/189456196
#include <lastweapon/number>
using namespace lastweapon;
const int N = int(5e2) + 9;
Int f[N][N], I[2*N], Fact[2*N]; // i step, min pref sum >= -(j-1)
int n; Int p, q;
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
//freopen("/Users/minakokojima/Documents/GitHub/ACM-Training/Workspace/out.txt", "w", stdout);
#endif
MOD = 998244353;
RD(n); p = Int(RD()) / 10000; q = Int(1) - p;
for (int i=(I[1]=1)+1; i<=2*n; i++) I[i] = 1ll * (MOD - MOD / i) * I[MOD%i] % MOD;
Fact[0] = 1; REP_1(i, n) Fact[i] = Fact[i-1] * i;
REP_1(i, n+1) f[0][i] = 1;
// \sum f[a] * (p*f[b]+q*f[b]) * \binom{i}{a, b+1}
REP_1(i, n) REP_1(j, n-i+1) {
REP(a, i) {
int b = i-1-a;
f[i][j] += f[a][j] * (p*f[b][j+1] + q*f[b][j-1]) * I[b+1];
}
}
Int z = f[n][1] * Fact[n]; REP_1(i, n) {
z *= I[i*2-1];
}
cout << z << endl;
}




Alca
Amber
Belleve Invis
Chensiting123
Edward_mj
Fotile96
Hlworld
Kuangbin
Liyaos
Lwins
LYPenny
Mato 完整版
Mikeni2006
Mzry
Nagatsuki
Neko13
Oneplus
Rukata
Seter
Sevenkplus
Sevenzero
Shirleycrow
Vfleaking
wangzhpp
Watashi
WJMZBMR
Wywcgs
XadillaX
Yangzhe
三途川玉子
About.me
Vijos
