C. Mixture

bfs

image

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

using namespace std;

void solve() {
    int n;
    string s;
    cin >> n >> s;
    
    int n2 = 1<<n;
    vector<bool> used(n2);
    used[0] = true;
    queue<int> q;
    q.push(0);
    
    while (q.size()) {
        int t = q.front(); q.pop();
        rep(i, n) {
            int nt = t | 1<<i;
            if (nt == t) continue;
            if (s[nt-1] == '1') continue;
            if (used[nt]) continue;
            used[nt] = true;
            q.push(nt);
        } 
    }
    
    if (used[n2-1]) puts("Yes");
    else puts("No");
}

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

D. Get Many Stickers

本质上就是用 \(1\) 张贴纸兑换 \(A_i-B_i\) 个空瓶的问题!
因此只需按照 \(A_i-B_i\) 从小到大的顺序贪心兑换即可。
由于 \(N\) 很大,需要通过数学计算确定兑换次数(这里稍微需要一点算术技巧)。

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

int main() {
    ll n; int m;
    cin >> n >> m;
    
    vector<P> ps;
    rep(i, m) {
        ll a, b;
        cin >> a >> b;
        ps.emplace_back(a-b, a);
    }
    ranges::sort(ps);
    
    ll ans = 0;
    for (auto [d, a] : ps) {
        if (n < a) continue;
        ll x = (n-a)/d+1;
        ans += x;
        n -= d*x;
    }
    
    cout << ans << '\n';
     
    return 0;
}

E. Hungry Takahashi

一种做法是二分答案,然后用dp来判定

dp[i][j] 表示在到达格子 \((i, j)\) 时所持有的硬币的最大值

代码实现
#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 h, w;
    cin >> h >> w;
    
    vector a(h, vector<int>(w));
    rep(i, h)rep(j, w) cin >> a[i][j];
    int n = h+w-1;
    vector<int> p(n);
    rep(i, n) cin >> p[i];
    
    const ll INF = 1e18;
    auto judge = [&](ll x) {
        vector dp(h, vector<ll>(w, -INF));
        dp[0][0] = x;
        rep(i, h)rep(j, w) {
            dp[i][j] += a[i][j] - p[i+j];
            if (dp[i][j] < 0) dp[i][j] = -INF;
            if (i+1 < h) chmax(dp[i+1][j], dp[i][j]);
            if (j+1 < w) chmax(dp[i][j+1], dp[i][j]);
        }
        return dp[h-1][w-1] >= 0;
    };
    
    ll ac = 1e15, wa = -1;
    while (ac-wa > 1) {
        ll wj = (ac+wa)/2;
        if (judge(wj)) ac = wj; else wa = wj;
    }
    
    cout << ac << '\n';
     
    return 0;
}

事实上不需要二分,可以考虑从后往前做dp
那么当前格子上的硬币就会从高桥手上回退到格子上,原本买食物花的硬币会回到高桥手上

代码实现
#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; } 
inline void chmin(ll& a, ll b) { if (a > b) a = b; } 

int main() {
    int h, w;
    cin >> h >> w;
    
    vector a(h, vector<int>(w));
    rep(i, h)rep(j, w) cin >> a[i][j];
    int n = h+w-1;
    vector<int> p(n);
    rep(i, n) cin >> p[i];
    
    const ll INF = 1e18;
    vector dp(h, vector<ll>(w, INF));
    dp[h-1][w-1] = 0;
    for (int i = h-1; i >= 0; --i) {
        for (int j = w-1; j >= 0; --j) {
            dp[i][j] += p[i+j] - a[i][j];
            chmax(dp[i][j], 0ll);
            
            if (i) chmin(dp[i-1][j], dp[i][j]);
            if (j) chmin(dp[i][j-1], dp[i][j]);
        }
    }
    
    cout << dp[0][0] << '\n';
     
    return 0;
}

F. Max Combo

线段树
需要对每个点维护以下信息:

  • 答案
  • 左端开始的极大同种字符长度
  • 右端开始的极大同种字符长度
  • 区间中的字符是否都相同
代码实现
#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;

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

struct S {
    int ans;
    char lc; int l;
    char rc; int r;
    bool same;
};

S op(S a, S b) {
    if (a.ans == 0) return b;
    if (b.ans == 0) return a;
    S res;
    res.ans = max(a.ans, b.ans);
    if (a.rc == b.lc) chmax(res.ans, a.r+b.l);
    res.lc = a.lc; res.l = a.l;
    res.rc = b.rc; res.r = b.r;
    if (a.same and a.lc == b.lc) res.l += b.l;
    if (b.same and a.rc == b.rc) res.r += a.r;
    res.same = a.same and b.same and a.lc == b.lc;
    return res;
}
S e() { return S(0, '?', 0, '?', 0, false); }
S newS(char c) { return S(1, c, 1, c, 1, true); }

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    string s;
    cin >> n >> q >> s;
    
    segtree<S, op, e> t(n);
    rep(i, n) t.set(i, newS(s[i]));
    
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            int i; char x;
            cin >> i >> x;
            --i;
            t.set(i, newS(x));
        }
        else {
            int l, r;
            cin >> l >> r;
            --l;
            cout << t.prod(l, r).ans << '\n';
        }
    }
    
    return 0;
}

G. Get Many Cola

本质上是每消耗 \(A_i-B_i\) 个空瓶可兑换 \(B_i\) 瓶饮料,这实际上是一个背包问题的变种!
对于此类背包问题,当背包容量超过 \(O(\text{物品重量}^2)\) 时,优先装入价值/重量比最大的物品是最优策略。
因此,背包容量上限为 \(O(A^2)\),对每个 \(A_i\),只需保留 \(B_i\) 最大的有效物品,使物品种类降至 \(O(B)\)
最终时间复杂度为 \(O(A^2B)\)

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

using namespace std;
using ll = long long;

template<class T>
inline void chmax(T& a, T b) { if (a < b) a = b; }

int main() {
    ll n; int M;
    cin >> n >> M;
    
    int m = 300;
    vector<int> b(m+1);
    rep(i, M) {
        int A, B;
        cin >> A >> B;
        chmax(b[A], B);
    }
    
    int x = 1;
    for (int i = 1; i <= m; ++i) {
        if (b[i]*(x-b[x]) > b[x]*(i-b[i])) x = i;
    }
    
    const ll INF = 1e18;
    int mx = x*m+1;
    vector<ll> dp(mx);
    
    rep(s, mx) {
        for (int i = 1; i <= m; ++i) {
            if (s < i) continue;
            chmax(dp[s], dp[s-i+b[i]]+b[i]);
        }
    }
    
    ll ans = n;
    if (n > mx) {
        ll t = (n-mx)/(x-b[x])+1;
        n -= (x-b[x])*t;
        ans += b[x]*t;
    }
    ans += dp[n];
    
    cout << ans << '\n';
    
    return 0;
}