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\) 处展开并规约:
- 如果 \(X > N\),则始终有 \(2k - N - X < 0\),期望值直接为 \(X\)。
- 如果 \(X < -N\),则始终有 \(2k - N - X > 0\),期望值直接为 \(-X\)。
- 如果 \(-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})\) 的时间内处理所有的组合数前缀和查询。
浙公网安备 33010602011771号