惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
小众软件
小众软件
Vercel News
Vercel News
Last Week in AI
Last Week in AI
H
Help Net Security
The Cloudflare Blog
L
LangChain Blog
Microsoft Security Blog
Microsoft Security Blog
B
Blog RSS Feed
云风的 BLOG
云风的 BLOG
I
InfoQ
U
Unit 42
美团技术团队
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 叶小钗
Y
Y Combinator Blog
Hugging Face - Blog
Hugging Face - Blog
A
About on SuperTechFans
宝玉的分享
宝玉的分享
量子位
博客园_首页

某岛

AtCoder Beginner Contest 409 Luogu P5325. 【模板】Min_25 筛 UOJ #188. 【UR #13】Sanrd AtCoder Beginner Contest 371 AtCoder Beginner Contest 369 RPGMaker 2k3 百科 OneShot 的考古 2024“开创拓芯”游戏创享节的相关记录 CJ 回来后的戒断反应 Luogu P10221. [省选联考 2024] 重塑时光 Luogu P5308 [COCI2018-2019#4] Akvizna wqs 二分 歌唱王国 Lean 相关 BZOJ 3153. Sone1 The 2023 ICPC World Finals Luxor 新巴别塔 Sora 的想象与思考 Facebook Hacker Cup 2023 Round 1 AtCoder Beginner Contest 322 LLaMA 2 相关 HuggingFace AI Game Jam ACL 2023 Trans 相关… Luogu P2053. [SCOI2007] 修车 Luogu P1973. [NOI2011] NOI 嘉年华 Luogu P1933. [NOI2010] 旅行路线 Luogu P1954. [NOI2010] 航空管制 Luogu P2048. [NOI2010] 超级钢琴 Luogu P2046. [NOI2010] 海拔
Codeforces Round #844
2023-01-17 · via 某岛

又是久违的 tourist round。。。

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,得到转移方程:

 f[i][j] = \sum f_2[a][j] (p \cdot f[b][j+1]+q\cdot f[b][j-1]) \binom{i-1}{a, 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

同样的,在转移过程中,括号序列允许不合法,我们进行同样的加维,只是这里因为我们已经建立了一一对应关系,转移时我们可以直接对这里的树结构进行计数。
我们直接枚举树的形态,再用组合数重新进行标号,得到更简单的转移方程:
 f[i][j] = \sum f[a][j] (p\cdot f[b][j+1]+q\cdot f[b][j-1]) \binom{i}{a, b+1}

注意这里的 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;
}

Posted by xiaodao
Category: 日常