C. Long Sequence

先找到 \(K\) 位于哪个整块,然后利用周期性找到它在这个整块里的哪个数

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

using namespace std;
using ll = long long;

int main() {
    int n; ll k;
    cin >> n >> k;
    --k;
    
    vector<vector<int>> a(n);
    rep(i, n) {
        int l;
        cin >> l;
        a[i] = vector<int>(l);
        rep(j, l) cin >> a[i][j];
    }
    
    vector<int> c(n);
    rep(i, n) cin >> c[i];
    
    rep(i, n) {
        ll len = (ll)a[i].size()*c[i];
        if (k < len) {
            k %= a[i].size();
            cout << a[i][k] << '\n';
            return 0;
        }
        k -= len;
    }
    
    return 0;
}

D. Raise Minimum

见到最小值最大直接二分答案,注意上限只需取到 \(2e18\) 即可

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

using namespace std;
using ll = long long;

ll divceil(ll a, ll b) {
    return (a+b-1)/b;
}

int main() {
    int n; ll k;
    cin >> n >> k;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
    
    auto judge = [&](ll x) {
        ll rem = k;
        rep(i, n) {
            if (x <= a[i]) continue;
            ll t = divceil(x-a[i], i+1);
            rem -= t;
            if (rem < 0) return false;
        }
        return true;
    };
    
    ll ac = 1, wa = ll(2e18)+1;
    while (abs(ac-wa) > 1) {
        ll wj = (ac+wa)/2;
        if (judge(wj)) ac = wj; else wa = wj;
    }
    
    cout << ac << '\n';
    
    return 0;
}

E. Crossing Table Cloth

要使两块布料 \(A(L_a, R_a)\)\(B(L_b, R_b)\) 的并集恰好为 \([S, T]\),必须满足以下两种情况之一:

  • 情况 \(1\):包含
    其中一块布料恰好就是 \([S, T]\),而另一块布料是 \([S, T]\) 的子集(即 \(S \le L_i \le R_i \le T\))。
  • 情况 \(2\):相交
    没有一块布料能单独覆盖 \([S, T]\)。此时必须一块布料从 \(S\) 开始(\(L_a = S, R_a < T\)),另一块布料到 \(T\) 结束(\(L_b > S, R_b = T\)),并且它们之间必须相连或重叠(即 \(R_a + 1 \ge L_b\))。
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

int lwb(const vector<int>& a, int x) {
    return lower_bound(a.begin(), a.end(), x) - a.begin();
}

int main() {
    int n, m;
    cin >> n >> m;
    n += 2;
    
    vector<vector<int>> rs(n), ls(n);
    const int INF = 1001001001;
    vector<int> minR(n, INF);
    rep(i, m) {
        int l, r;
        cin >> l >> r;
        rs[l].push_back(r);
        ls[r].push_back(l);
        minR[l] = min(minR[l], r);
    }
    rep(i, n) ranges::sort(rs[i]);
    rep(i, n) ranges::sort(ls[i]);
    for (int i = n-2; i >= 0; --i) minR[i] = min(minR[i], minR[i+1]);
    
    int q;
    cin >> q;
    rep(qi, q) {
        int l, r;
        cin >> l >> r;
        auto solve = [&]() {
            int cnt = lwb(rs[l], r+1)-lwb(rs[l], r);
            if (cnt) {
                if (minR[l] < r or minR[l+1] <= r) return true;
                return cnt >= 2;
            }
            else {
                int ri = lwb(rs[l], r)-1;
                int li = lwb(ls[r], l);
                if (ri < 0 or li >= ls[r].size()) return false;
                return rs[l][ri]+1 >= ls[r][li];
            }
        };
        if (solve()) puts("Yes"); else puts("No");
    }
    
    return 0;
}

F. Second Gap

\(dp[j]\) 表示:在当前的后缀 \(P[i \dots N-1]\) 中,最大的两个元素分别位于下标 \(j\)\(j + D_i\) 时的合法排列方案数。

状态转移:

当我们向左扩展,加入位置 \(i\) 的元素 \(P_i\) 时,会有两种情况:

  • 情况 \(1\)\(P_i\) 是一个小数字(不是 \(\text{Top-2}\)
    • 这意味着后缀 \(i\)\(\text{Top-2}\) 和后缀 \(i+1\)\(\text{Top-2}\) 是同一对数字。
    • 约束:由于 \(\text{Top-2}\) 没变,它们的距离必须依然符合新条件,即 \(D_i = D_{i+1}\)。如果 \(D_i \neq D_{i+1}\),这种情况方案数为 0。
    • 计数:\(P_i\) 可以从当前剩下的 \(N-i-2\) 个较小值中任选一个。
    • 转移:\(dp_{new}[j] = dp_{old}[j] \times (N-i-2)\)
  • 情况 \(2\)\(P_i\) 是一个大数字(进入 \(\text{Top-2}\)
    • 这意味着 \(P_i\) 占据了 \(\text{Top-2}\) 中的一个位置(下标为 \(i\))。
    • 根据条件,另一个 \(\text{Top-2}\) 必须在下标 \(i + D_i\)
    • 这要求在旧的后缀 \(i+1\) 中,原本的最大值必须已经位于 \(i + D_i\)
    • 转移:提取旧状态中所有包含位置 \(i + D_i\) 的方案,转移到新状态 \(dp[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;

mint op(mint a, mint b) { return a+b; }
mint e() { return 0; }
mint mapping(mint f, mint x) { return x*f; }
mint composition(mint f, mint g) { return f*g; }
mint id() { return 1; }

int main() {
    int n;
    cin >> n;
    
    vector<int> d(n-1);
    rep(i, n-1) cin >> d[i];
    
    if (d[n-2] != 1) {
        cout << 0 << '\n';
        return 0;
    }
    
    lazy_segtree<mint, op, e, mint, mapping, composition, id> seg(n);
    
    seg.set(n-1, 1);
    seg.set(n-2, 1);
    for (int i = n-3; i >= 0; --i) {
        mint x = seg.get(i+d[i]);
        if (d[i] != d[i+1]) {
            seg.apply(0, n, 0);
        }
        else {
            seg.apply(0, n, n-i-2);
        }
        seg.set(i, x);
        seg.set(i+d[i], seg.get(i+d[i])+x);
    }
    
    mint ans = seg.all_prod();
    cout << ans.val() <<'\n';
    
    return 0;
}

G. Catch All Apples

假设机器人捡完苹果 \(i\) (\(T_i, X_i\)) 后去捡苹果 \(j\) (\(T_j, X_j\)),且 \(T_j \ge T_i\)。机器人的速度最大为 \(1\),所以它在时间差内能跑的最远距离必须大于等于坐标差:$$|X_j - X_i| \le T_j - T_i$$拆开绝对值得到两个不等式:

  • \(X_j - X_i \le T_j - T_i \implies \mathbf{T_i + X_i \le T_j + X_j}\)
  • \(-(X_j - X_i) \le T_j - T_i \implies \mathbf{T_i - X_i \le T_j - X_j}\)

注:其实就是将坐标系旋转 \(45^{\circ}\),将切比雪夫距离转化成曼哈顿距离

这意味着,如果我们将每个苹果转化为二维坐标 \((T+X, T-X)\),那么“一个机器人能连续捡到两个苹果”的充要条件就是:这两个点在二维空间中满足偏序关系(即一个点的横纵坐标都小于等于另一个点)。

题目要求的是最少机器人数量,即用最少条“路径”覆盖所有的点。
根据 Dilworth 定理:

最小路径覆盖数 = 最大反链长度

在本题中,“反链”意味着一组苹果,其中任何两个苹果都不能被同一个机器人捡到。即对于反链中的任意 \(i, j\),不满足偏序关系。

  • 偏序关系:\(A_i \le A_j\)\(B_i \le B_j\)
  • 反链关系:\(A_i < A_j\)\(B_i > B_j\)(或反之)

因此,我们只需要找到一组点,使得当横坐标 \(A\) 递增时,纵坐标 \(B\) 严格递减。这个最长的反链长度,就是我们要找的答案。

经典二维偏序问题,通过排序可以去掉第一维,然后只需求 \(B\) 的最长下降子序列即可。事实上,将 \(B_i\) 改成 \(3e5-B_i\) 就变成了求它的最长上升子序列。

代码实现
#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;
    cin >> n;
    
    const int m = 3e5;
    vector<P> ps;
    rep(i, n) {
        int t, p;
        cin >> t >> p;
        int x = t+p, y = m-(t-p);
        ps.emplace_back(x, -y);
    }
    sort(ps.begin(), ps.end());
    
    vector<int> dp(n, m*2+5);
    int ans = 0;
    for (auto [x, y] : ps) {
        y = -y;
        int i = lower_bound(dp.begin(), dp.end(), y) - dp.begin();
        dp[i] = y;
        ans = max(ans, i+1);
    }
    
    cout << ans <<'\n';
    
    return 0;
}