某岛

… : "…アッカリ~ン . .. . " .. .
May 29, 2023

Codeforces Round #875

久违地冲了一局。。。无悬念的差点爆 0 了。

Problem A. Copil Copac Draws Trees

题意:给定一个树,和边,初始根节点是点亮的,每回合你可以按照边地顺序扫一遍所有边,如果其中一个点是点亮的,那么点亮另一个点。问多少回合后所有点被点亮。

分析:显然不考虑边顺序的话,就是求树的深度,这个过程也可以看作树 dp。。
dp[i] 存两个值,分别表示最少被点亮所需的回合数,和被点亮时所用边的编号。转移的时候讨论一下编号即可。

Problem C. Hyperregular Bracket Strings

题意:问有多少长度 n 的合法括号序列,满足给定的所有区间也时合法的括号序列。

分析:个人感觉非常棒的题目。首先不考虑区间的话就是卡塔兰数 {C_i}。

然后考察 [1,4] [2,3] 和 [1,3] [2,4] 两种情况,前者比较容易分析,[2,3] 现在一定是一个独立的合法括号序列,[1]-[4] 然后两个位置虽然不连续,但是也构成一个独立的合法括号序列。而后者 [2,3] 则要求 [1] [2,3] [4] 分别是三组独立的卡塔兰序列,因为中间重合的部分对前缀和的 delta 一定是 0(反证法)。

这样看的话最后结果一定是若干 {C_i} 相乘,难点就是分解出这些独立的区间的测度,看起来结构简单,写起来却并不容易,我们无法像表达式解析那样简单的递归或者用一个栈,考虑到还有区间共享端点的情况就更麻烦了。

然后有一个非常巧妙地思路。。。给每个区间随机分配一个 uint 数,然后把前缀和改成 xor 前缀和,然后用 hash[] 记录每种区间集合影响下的测度数,就能轻易处理不连续的状态,并区分上面说到的嵌套和重叠两种情况了。

这种 hash 的思想非常实用,比如去年 FHQ R2 的 A 题 也出现过类似的思路。

具体说来就是开三个东西:

map<int, uint64_t> H; // 记录每个区间的随机 hash 映射
map<uint64_t, int> cnt; // 记录每个区间交的格子数
uint64_t S[N]; // 记录前缀和对应的区间交

如果区间稀疏的话,还需要对前缀和离散化。

#include <lastweapon/poly>
#include <bits/stdc++.h>
using namespace lastweapon;

mt19937_64 gen(chrono::steady_clock::now().time_since_epoch().count());
uniform_int_distribution<uint64_t> rnd(0, ULLONG_MAX);

const int N = int(3e5) + 9;
Mint C[N]; uint64_t S[N];
int n, k;

Mint binom(int n, int m) {
    return fac[n] * invFac[m] * invFac[n-m];
}

map<int, uint64_t> H;
map<uint64_t, int> cnt;
uint64_t h(int x) {
    if (!CTN(H, x)) H[x] = rnd(gen);
    return H[x];
}

int main(){

#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif

    FOR(i, 1, N/2) C[i*2] = binom(2*i,  i) - binom(2*i, i+1);

    Rush {
        RD(n, k);

        H.clear(), cnt.clear(); fill(S+1, S+n+1, 0);

        REP(i, k) {
            int a, b; RD(a, b);
            S[a] ^= h(i); S[b+1] ^= h(i);
        }

        REP_1(i, n) ++cnt[S[i] ^= S[i-1]];
        Mint z = 1; for (auto it: cnt) z *= C[it.se];

        cout << z << endl;
    }
}