C. Tallest at the Moment

利用离开的时间 \(L_i\) 已经按非降序排列的性质,把“在时刻 \(t+1\) 仍在房间的人”都对应为一个后缀区间,然后对每个后缀预处理最大高度,查询时用二分找到该后缀起点即可。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

int main() {
    int n;
    cin >> n;
    
    vector<int> h(n), l(n);
    rep(i, n) cin >> h[i] >> l[i];
    
    vector<int> d(n);
    d[n-1] = h[n-1];
    for (int i = n-2; i >= 0; --i) {
        d[i] = max(d[i+1], h[i]);
    }
    
    int q;
    cin >> q;
    rep(qi, q) {
        int t;
        cin >> t;
        int li = upper_bound(l.begin(), l.end(), t) - l.begin();
        cout << d[li] << '\n';
    }
    
    return 0;
}

D. Maximize the Gap

二分答案最小两两距离 \(w\),对每个候选 \(w\) 用贪心判断能否选出至少 \(K\) 个互不重叠且两两距离 \(\geqslant w\) 的区间。

其实这题是区间调度问题的一个变种

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using P = pair<int, int>;

int main() {
    int n, k;
    cin >> n >> k;
    
    vector<P> rl;
    rep(i, n) {
        int l, r;
        cin >> l >> r;
        rl.emplace_back(r, l);
    }
    
    ranges::sort(rl);
    
    const int INF = 1001001001;
    auto judge = [&](int w) {
        int r_max = -INF, num = 0;
        for (auto [r, l] : rl) {
            r += w;
            if (r_max <= l) {
                num++;
                r_max = r;
            }
        }
        return num >= k;
    };
    
    int ac = 0, wa = INF;
    while (ac+1 < wa) {
        int wj = (ac+wa)/2;
        if (judge(wj)) ac = wj; else wa = wj;
    }
    
    if (ac == 0) ac = -1;
    cout << ac << '\n';
    
    return 0;
}

E. Roads and Gates

把“任意 \(i→j\) 的传送耗时 \(X_i +X_j +Y\)”用一个额外的虚拟节点建图表示,然后在这个图上跑 \(\text{Dijkstra}\) 求最短路。

代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using P = pair<ll, int>;

int main() {
    int n, m, y;
    cin >> n >> m >> y;
    
    vector<vector<pair<int, int>>> g(n+1);
    rep(i, m) {
        int a, b, t;
        cin >> a >> b >> t;
        --a; --b;
        g[a].emplace_back(b, t);
        g[b].emplace_back(a, t);
    }
    
    rep(i, n) {
        int x;
        cin >> x;
        g[i].emplace_back(n, x);
        g[n].emplace_back(i, x+y);
    }
    
    const ll INF = 1e18;
    vector<ll> dist(n+1, INF);
    priority_queue<P, vector<P>, greater<P>> q;
    auto push = [&](int v, ll x) {
        if (dist[v] <= x) return;
        dist[v] = x;
        q.emplace(x, v);
    };
    push(0, 0);
    
    while (q.size()) {
        auto [x, v] = q.top(); q.pop();
        if (dist[v] != x) continue;
        for (auto [u, w] : g[v]) {
            push(u, x+w);
        }
    }
    
    rep(i, n) if (i) {
        cout << dist[i] << " \n"[i == n-1];
    }
    
    return 0;
}

F. Senshuraku

最终全场的最高胜场 \(W\) 只有可能为 \(mx\)\(mx+1\)(其中 \(mx\) 为当前最大胜场)。我们可以将问题拆解为计算 \(W=mx\)\(W=mx+1\) 两种情况下的概率和。

对于确定的最高胜场 \(w\),逐场检查:

  • 若某玩家当前胜场已达 \(w\),为了不超过 \(w\),他本场必须输,这意味着他的对手必须赢。
  • 若不满足上述强制条件(如两个已经 \(w\) 胜场的人相遇,无论谁赢都会产生 \(w+1\)),则说明 \(W=w\) 这种情况不成立,贡献为 0。

分类统计与贡献法:

  • 将所有玩家根据“是否必然达到 \(w\) 胜场”和“是否以 \(\frac 12\) 概率达到 \(w\) 胜场”分类。
  • 设必然达到 \(w\) 的人数为 base,以 \(\frac 12\) 概率达到的独立玩家人数为 add
  • 枚举 add 中有 \(i\) 个人成功达到 \(w\),利用二项分布和组合数 \(\binom{add}{i}\) 计算概率。此时总人数为 \(base + i\),每个人夺冠的概率贡献为 \(\frac{1}{base + i}\)
代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using mint = modint998244353;

struct modinv {
  int n; vector<mint> d;
  modinv(): n(2), d({0,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(-d[mint::mod()%n]*(mint::mod()/n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} invs;
struct modfact {
  int n; vector<mint> d;
  modfact(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*n), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} facts;
struct modfactinv {
  int n; vector<mint> d;
  modfactinv(): n(2), d({1,1}) {}
  mint operator()(int i) {
    while (n <= i) d.push_back(d.back()*invs(n)), ++n;
    return d[i];
  }
  mint operator[](int i) const { return d[i];}
} ifacts;
mint comb(int n, int k) {
  if (n < k || k < 0) return 0;
  return facts(n)*ifacts(k)*ifacts(n-k);
}

int main() {
    int n;
    cin >> n;
    
    int n2 = n*2;
    vector<int> a(n2);
    rep(i, n2) cin >> a[i];
    
    int mx = ranges::max(a);
    
    vector<mint> ans(n2);
    auto f = [&](int w) {
        vector<int> d(n2); // 0: never, 1: 1/2, 2: always, 3: additional
        mint p = 1;
        rep(i, n) {
            int li = i*2, ri = li+1;
            int l = a[li], r = a[ri];
            if (l > r) swap(l, r), swap(li, ri);
            if (r+1 < w) continue;
            if (l+1 > w) return;
            p *= invs(2);
            if (r == w) { // l win
                d[ri] = 2;
                if (l == w-1) d[li] = 2;
            } 
            else {
                // r = w-1
                if (l == w-1) d[li] = d[ri] = 1, p *= 2;
                else d[ri] = 3;
            }  
        }
        
        int base = 0;
        rep(i, n2) {
            if (d[i] == 1 or d[i] == 2) base += d[i];
        }
        base /= 2;
        int add = 0;
        rep(i, n2) if (d[i] == 3) add++;
        {
            mint now;
            rep(i, add+1) {
                if (base+i) now += comb(add, i)*invs(base+i);
            }
            now *= p;
            rep(i, n2) {
                if (d[i] == 1 or d[i] == 2) ans[i] += now*d[i]*invs(2);
            }
        }
        {
            mint now;
            rep(i, add) {
                now += comb(add-1, i)*invs(base+i+1);
            }
            now *= p;
            rep(i, n2) {
                if (d[i] == 3) ans[i] += now;
            }
        }
    };
    
    f(mx); f(mx+1);
    
    rep(i, n2) cout << ans[i].val() << " \n"[i == n2-1];
    
    return 0;
}

G. Random Walk Distance

首先,建立数学模型。设向正方向走的步数为 \(k\),则向负方向走的步数为 \(N-1\)。最终到达的坐标 \(x'\) 可以表示为:$$x' = k - (N - k) = 2k - N$$

其中 \(k\) 服从二项分布 \(k \sim \text{Binomial}(N, \frac{1}{2})\)。因此,我们要求的目标期望值可以写成:$$E[|x' - X|] = \frac{1}{2^N} \sum_{k=0}^N \binom{N}{k} |2k - N - X|$$

\(f(N, X) = \sum\limits_{k=0}^N \binom{N}{k} |2k - N - X|\)。通过绝对值符号的讨论和组合数恒等式 \(\sum k\binom{N}{k} = N\sum \binom{N-1}{k-1}\) 的化简,我们可以将该式在 \(K = \lfloor \frac{N+X}{2} \rfloor\) 处展开并规约:

  1. 如果 \(X > N\),则始终有 \(2k - N - X < 0\),期望值直接为 \(X\)
  2. 如果 \(X < -N\),则始终有 \(2k - N - X > 0\),期望值直接为 \(-X\)
  3. 如果 \(-N \le X \le N\),式子可以化简为:$$f(N, X) = X \left( 2 \cdot S(N, K) - 2^N \right) + 2N \cdot \binom{N-1}{K}$$其中 \(S(N, K) = \sum\limits_{k=0}^K \binom{N}{k}\) 是组合数的前缀和。

我们无法在 \(O(1)\) 时间内直接求出任意的 \(S(N, K)\)。但注意到从 \(S(N, K)\) 转移到相邻状态的复杂度为 \(O(1)\)

  • \(S(N, K+1) = S(N, K) + \binom{N}{K+1}\)
  • \(S(N+1, K) = 2S(N, K) - \binom{N}{K}\)

因此,我们可以离线所有询问,并用莫队在 \(O((T + N)\sqrt{N})\) 的时间内处理所有的组合数前缀和查询。