C. Not All Covered

差分

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

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> s(n+1);
    rep(i, m) {
        int l, r;
        cin >> l >> r;
        --l;
        s[l]++; s[r]--;
    }
    
    rep(i, n) s[i+1] += s[i];
    
    int ans = *min_element(s.begin(), s.begin()+n);
    cout << ans << '\n';
    
    return 0;
}

D. Flip to Gather

选择某个区间 \([l, r]\),将 \([l, r]\) 中所有 0 翻转成 1,将 \([l, r]\) 之外的 1 翻转成 \(0\),将二者的数量相加就是候选答案。但这个是 \(O(N^2)\)

比较简单的做法是 dp

对于选定的区间染上颜色 \(1\),区间左边染上颜色 \(0\),区间右边染上颜色 \(2\)
为了让区间左边都染上 \(0\),区间左边就要消耗翻转 1 的费用
为了让区间都染上 \(1\),区间就要消耗翻转 0 的费用
为了让区间右边都染上 \(2\),区间右边就要消耗翻转 1 的费用

dp[i][j] 表示到第 \(i\) 个位置位置末尾位置染上颜色 \(j\) 时的最小费用

时间复杂度为 \(O(N)\)

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

using namespace std;

inline void chmin(int& a, int b) { if (a > b) a = b; }

void solve() {
    int n;
    string s;
    cin >> n >> s;
    
    const int INF = 1001001001;
    vector dp(n+1, vector<int>(3, INF));
    dp[0][0] = 0;
    rep(i, n) {
        rep(j, 3) {
            int cost = (s[i]-'0') == (j%2) ? 0 : 1;
            rep(pj, j+1) chmin(dp[i+1][j], dp[i][pj]+cost);
        }
    }
    
    int ans = ranges::min(dp[n]);
    cout << ans << '\n';
}

int main() {
    int t;
    cin >> t;
    
    while (t--) solve();
    
    return 0;
}

E. Minimum OR Path

按位考虑
为了使得答案最小,应该尽可能地让高位取到 \(0\)
所以应该从高位开始考虑
固定某一位 \(k\),对于每个边权 \(c\),如果答案在不考虑后 \(k\) 位得到的值覆盖 \(c\) 去掉后 \(k\) 位得到的值(也就是子集关系),那么就留下这条边,否则直接删掉,这样就得到了一张新图,然后判定新图上点 \(1\)\(N\) 是否连通即可。如果连通,那么答案第 \(k\) 位上就可以取 \(0\)。可以用并查集来维护连通性。

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

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<tuple<int, int, int>> es;
    rep(i, m) {
        int a, b, c;
        cin >> a >> b >> c;
        --a; --b;
        es.emplace_back(a, b, c);
    }
    
    int ans = 0;
    for (int i = 29; i >= 0; --i) {
        dsu uf(n);
        for (auto [a, b, c] : es) {
            if (((c>>i)|(ans>>i)) != (ans>>i)) continue;
            uf.merge(a, b);
        }
        if (!uf.same(0, n-1)) ans |= 1<<i;
    }
    
    cout << ans << '\n';
    
    return 0;
}

F. Athletic

dp[i] 表示从第 \(i\) 个脚踏架开始能移动的最大次数
然后用线段树加速一下即可
这个线段树要求支持单点加,区间 \(\max\)

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

using namespace std;

int op(int a, int b) { return max(a, b); }
int e() { return -1; }

int main() {
    int n, D, R;
    cin >> n >> D >> R;
    
    vector<int> H(n);
    rep(i, n) cin >> H[i], H[i]--;
    
    vector<int> hi(n);
    rep(i, n) hi[H[i]] = i;
    
    vector<int> dp(n);
    segtree<int, op, e> rmq(n);
    rep(h, n) {
        if (h >= D) {
            int j = hi[h-D];
            rmq.set(j, dp[j]);
        }
        int i = hi[h];
        int l = max(0, i-R), r = min(n, i+R+1);
        dp[i] = rmq.prod(l, r)+1;
    }
    
    int ans = ranges::max(dp);
    cout << ans << '\n';
    
    return 0;
}

G. A/B < p/q < C/D

\(\operatorname{Stern-Brocot}\) 树上二分