C. Sake or Water

先将数组 \(A\) 做降序排序
显然容量最大的 \(N-K\) 杯液体必选,然后清酒也可能出现在后 \(K\) 个杯子里,我们可以对这后 \(k\) 杯液体从前往后贪心地选择,直到喝到的容量达到 \(X\) 毫升为止。

代码实现
#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, k; ll x;
    cin >> n >> k >> x;
    
    vector<ll> a(n);
    rep(i, n) cin >> a[i];
    
    sort(a.begin(), a.end(), greater<>());
    
    ll sum = 0;
    for (int i = n-k; i < n; ++i) {
        sum += a[i];
        if (sum >= x) {
            cout << i+1 << '\n';
            return 0;
        }
    }
    
    puts("-1");
    
    return 0;
}

D. Paid Walk

注意到每个点的出度最多 \(4\),而 \(O(4^L) \leqslant 1e6\),所以直接爆搜即可

代码实现
#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, m, l, s, t;
    cin >> n >> m >> l >> s >> t;
    
    vector<vector<P>> g(n); 
    rep(i, m) {
        int a, b, c;
        cin >> a >> b >> c;
        --a; --b;
        g[a].emplace_back(b, c);
    }
    
    vector<bool> ans(n);
    auto f = [&](auto& f, int v, int rem, int sum) -> void {
        if (rem == 0) {
            if (s <= sum and sum <= t) ans[v] = true;
            return;
        }
        for (auto [u, w] : g[v]) {
            f(f, u, rem-1, sum+w);
        }
    };
    f(f, 0, l, 0);
    
    rep(i, n) if (ans[i]) cout << i+1 << ' ';
    
    return 0;
}

E. A > B substring

考虑生成一个整数序列 \(a\),将 A 记为 \(1\)\(B\) 记为 \(-1\)\(C\) 记为 \(0\)
\(S_i = a_1 + a_2 + \cdots + a_i\)

区间 \([l, r]\)\(A\) 的个数大于 \(B\) 的个数,等价于 \(S_r-S_{l-1} > 0 \Leftrightarrow S_r > S_{l-1}\)
那么现在的问题就变成了统计 \(l < r\)\(S_r > S_{l-1}\) 的经典二维数点问题
直接上树状数组即可

代码实现
#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 ll = long long;

int main() {
    int n;
    string s;
    cin >> n >> s;
    
    vector<int> d(n+1);
    d[0] = n;
    rep(i, n) {
        d[i+1] = d[i];
        if (s[i] == 'A') d[i+1]++;
        if (s[i] == 'B') d[i+1]--;
    }
    
    fenwick_tree<int> t(n*2+1);
    ll ans = 0;
    rep(i, n+1) {
        ans += t.sum(0, d[i]);
        t.add(d[i], 1);
    }
    
    cout << ans << '\n';
    
    return 0;
}

F. Must Buy

两种做法
经典做法是预处理出从左到右和从右到左的 \(01\) 背包 \(dp\),然后将两个拼起来
另外一种做法是考虑分治,如果断开的点在左半部分,那么就对右半部分正常跑 \(01\) 背包;如果断开的点在右半部分,那么就对左半部分正常跑 \(01\) 背包。时间复杂度为 \(\mathcal{O}(MN\log N)\)

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

using namespace std;
using ll = long long;

inline void chmax(ll& a, ll b) { if (a < b) a = b; }

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> p(n), v(n);
    rep(i, n) cin >> p[i] >> v[i];
    
    auto calcDP = [&]() {
        vector dp(n+1, vector<ll>(m+1));
        rep(i, n) {
            dp[i+1] = dp[i];
            rep(j, m+1-p[i]) chmax(dp[i+1][j+p[i]], dp[i][j]+v[i]);
        }
        return  dp;
    };
    
    auto dpl = calcDP();
    reverse(p.begin(), p.end());
    reverse(v.begin(), v.end());
    auto dpr = calcDP();
    reverse(p.begin(), p.end());
    reverse(v.begin(), v.end());
    reverse(dpr.begin(), dpr.end());
    
    auto getMax = [&](vector<ll>& dl, vector<ll> dr, int mx) {
        ll res = 0;
        rep(i, mx+1) chmax(res, dl[i]+dr[mx-i]);
        return res;
    };
    
    string ans;
    rep(i, n) {
        ll a = getMax(dpl[i], dpr[i+1], m-p[i]) + v[i];
        ll c = getMax(dpl[i], dpr[i+1], m);
        if (a == c) ans += 'B';
        else if (a > c) ans += 'A';
        else ans += 'C';
    }
    
    cout << ans << '\n';
    
    return 0;
}
代码实现2
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

inline void chmax(ll& a, ll b) { if (a < b) a = b; }

int main() {
    int n, m;
    cin >> n >> m;
    
    vector<int> p(n), v(n);
    rep(i, n) cin >> p[i] >> v[i];
    
    string ans;
    
    auto add = [&](vector<ll>& dp, int i) {
        for (int j = m-p[i]; j >= 0; --j) {
            chmax(dp[j+p[i]], dp[j]+v[i]);
        }
    };
    auto f = [&](auto& f, int l, int r, vector<ll> dp) -> void {
        if (r-l == 1) {
            ll a = dp[m-p[l]]+v[l];
            ll c = dp[m];
            if (a == c) ans += 'B';
            else if (a > c) ans += 'A';
            else ans += 'C';
            return;
        }
        int c = (l+r)/2;
        {
            auto nd = dp;
            for (int i = c; i < r; ++i) add(nd, i);
            f(f, l, c, nd);
        }
        {
            auto nd = dp;
            for (int i = l; i < c; ++i) add(nd, i);
            f(f, c, r, nd);
        }
    };
    f(f, 0, n, vector<ll>(m+1));
    
    cout << ans << '\n';
    
    return 0;
}

G. Takoyaki and Flip

延迟线段树的板子
对于半群信息,需要维护:

  • 最大值
  • 面朝上的盘子数
  • 面朝下的盘子数

对于作用信息,需要维护:

  • 待增加的值
  • 翻转次数
代码实现
#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 ll = long long;

struct S {
    ll mx; int up, down;
};
struct F {
    ll add; int flip; 
};

S op(S a, S b) {
    return S(max(a.mx, b.mx), a.up+b.up, a.down+b.down);
}
S e() { return S(0, 0, 0); }

S mapping(F f, S x) {
    if (f.flip) {
        x.mx = 0;
        if (f.flip%2) swap(x.up, x.down);
    }
    if (x.up) x.mx += f.add;
    return x;
}

F composition(F f, F g) {
    if (f.flip == 0) f.add += g.add;
    f.flip += g.flip;
    return f;
}
F id() { return F(0, 0); }

int main() {
    int n, q;
    cin >> n >> q;
    
    lazy_segtree<S, op, e, F, mapping, composition, id> t(vector<S>(n, S(0, 1, 0)));
    
    rep(qi, q) {
        int type, l, r;
        cin >> type >> l >> r;
        --l;
        if (type == 1) {
            int x;
            cin >> x;
            t.apply(l, r, F(x, 0));
        }
        if (type == 2) {
            t.apply(l, r, F(0, 1));
        }
        if (type == 3) {
            ll ans = t.prod(l, r).mx;
            cout << ans << '\n';
        }
    }
    
    return 0;
}