C. Cookies and Greedy Takahashi

分别对负数和非负数维护一个递减的队列和一个递增的队列

代码实现
#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;
    cin >> n;
    
    vector<int> l, r;
    rep(i, n) {
        int a;
        cin >> a;
        if (a < 0) l.push_back(a);
        else r.push_back(a);
    }
    
    sort(l.begin(), l.end(), greater<>());
    sort(r.begin(), r.end());
    
    queue<ll> ql, qr;
    for (int x : l) ql.push(x);
    for (int x : r) qr.push(x);
    const ll INF = 1e18;
    ql.push(-INF);
    qr.push(INF);
    
    ll x = 0, ans = 0;
    rep(k, n) {
        if (x-ql.front() <= qr.front()-x) {
            ans += x-ql.front();
            x = ql.front();
            ql.pop();
        }
        else {
            ans += qr.front()-x;
            x = qr.front();
            qr.pop();
        }
    }
    
    cout << ans << '\n';
    
    return 0;
}

D. Chargers

假设在时间 \(t_{in}\) 插入了一只初始电量为 \(w\) 的电池。
在随后的任意时刻 \(t \ge t_{in}\),该电池的电量为:$$\min(V, w + (t - t_{in})) = \min(V, (w - t_{in}) + t)$$注意到对于任何电池,在时刻 \(t\),其电量仅由常量值 \(K = w - t_{in}\) 决定:

  • 由于 \(f(K) = \min(V, K + t)\) 是关于 \(K\) 的单调递增函数;
  • 因此,在任意时刻 \(t\),当前电量最高的电池必定对应 \(K = w - t_{in}\) 最大的电池。

我们可以维护一个大根堆存储所有已插入电池的 \(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 Q, V;
    cin >> Q >> V;
    
    priority_queue<ll> q;
    rep(qi, Q) {
        int type, t;
        cin >> type >> t;
        if (type == 1) {
            ll w;
            cin >> w;
            q.push(w-t);
        }
        else {
            if (!q.size()) cout << -1 << '\n';
            else {
                ll ans = min<ll>(V, q.top()+t); q.pop();
                cout << ans << '\n';
            }
        }
    }
    
    return 0;
}

E. Sum of Square of Sum

设选出的 \(K\) 个球的集合为 \(S\),其得分为:$$\left( \sum_{i \in S} A_i \right)^2 = \sum_{i \in S} A_i^2 + 2 \sum_{\overset{i, j \in S}{i < j}} A_i A_j$$考虑所有 \(\binom{N}{K}\) 种选择方案的总和:

  • 单个元素的平方项 \(A_i^2\):每个球 \(A_i\) 会在 \(\binom{N-1}{K-1}\) 种选法中出现,因此这部分的总和为:$$\binom{N-1}{K-1} \sum_{i=1}^N A_i^2$$
  • 交叉项 \(2 A_i A_j\):每对不同的球 \((A_i, A_j)\) 会在 \(\binom{N-2}{K-2}\) 种选法中同时出现(当 \(K \ge 2\) 时)。注意到:$$2 \sum_{1 \le i < j \le N} A_i A_j = \left( \sum_{i=1}^N A_i \right)^2 - \sum_{i=1}^N A_i^2$$
    因此交叉项的总和为:$$\binom{N-2}{K-2} \left( \left( \sum_{i=1}^N A_i \right)^2 - \sum_{i=1}^N A_i^2 \right)$$

\(S_1 = \sum\limits_{i=1}^N A_i\)\(S_2 = \sum\limits_{i=1}^N A_i^2\),则最终答案为:$$\binom{N-1}{K-1} \cdot S_2 + \binom{N-2}{K-2} \cdot (S_1^2 - S_2)$$

代码实现
#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, k;
    cin >> n >> k;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    mint s, s2;
    rep(i, n) s += a[i];
    rep(i, n) s2 += mint(a[i])*a[i];
    
    mint ans;
    ans += s2*comb(n-1, k-1);
    ans += (s*s-s2)*comb(n-2, k-2);
    
    cout << ans.val() << '\n';
    
    return 0;
}

F. Concat (maximize)

将全部 \(N\) 个字符串按长度从大到小排序(长度相同时按字典序降序排序)。

直接选取排序后的前 \(K\) 个字符串。这 \(K\) 个字符串能提供理论上最大的总位数。

  • 拼接顺序:对选出的 \(K\) 个字符串,按自定义比较规则排序(若 \(A+B > B+A\),则 \(A\) 应该排在 \(B\) 前面),然后顺序拼接。

若选出的 \(\text{Top} K\) 个字符串全是零(例如 \(00, 000\)),拼接去零后值为 \(0\)

  • 此时若剩余的 \(N-K\) 个字符串中存在非零数,应放弃 \(\text{Top} K\) 中最短的那个字符串(即第 \(K\) 个字符串),从剩余字符串中挑选一个非零且数值最大的字符串补入,重新按 \(A+B > B+A\) 拼接。

比较上述两种情况拼接并去除前导零后的结果(先比较长度,长度相同比字典序),较大的即为最终答案。

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

using namespace std;

int main() {
    int n, k;
    cin >> n >> k;
    
    vector<string> s(n);
    rep(i, n) cin >> s[i];
    
    auto cmp = [&](const string& a, const string& b) {
        if (a.size() == b.size()) return a > b;
        return a.size() > b.size();
    };
    sort(s.begin(), s.end(), cmp);
    
    string ans = "0";
    auto upd = [&](vector<string> s) {
        sort(s.begin(), s.end(), [&](auto string& a, auto& string& b) {
            return a+b > b+a;
        });
        string now;
        for (string a : s) now += a;
        reverse(now.begin(), now.end());
        while (now.size() > 1 and now.back() == '0') now.pop_back();
        reverse(now.begin(), now.end());
        if (cmp(now, ans)) ans = now;
    };
    upd(vector<string>(s.begin(), s.begin()+k));
    {
        string best = "0"; 
        for (int i = k; i < n; ++i) {
            if (stoll(best) < stoll(s[i])) best = s[i];
        }
        vector<string> t(s.begin(), s.begin()+(k-1));
        t.push_back(best);
        upd(t);
    }
    
    cout << ans << '\n';
    
    return 0;
}

G. Caeser Syllables

一个“音节”对应一个元音连续段。一个位置 \(i\) 是某个元音连续段的起点,当且仅当:

  1. \(A'_i\) 是元音;
  2. \(i = 1\) 或者 \(A'_{i-1}\) 不是元音。

因此,对于循环平移 \(k\)

  • \(i = 1\) 时,贡献为 \(1\) 当且仅当 \(V[(A_1 + k) \bmod K] = 1\)
  • \(i > 1\) 时,相邻元素对 \((A_{i-1}, A_i)\) 产生 \(1\) 的贡献当且仅当 \(V[(A_{i-1} + k) \bmod K] = 0\)\(V[(A_i + k) \bmod K] = 1\)

于是,总音节数公式为:

\[[V[A'_1] = 1] + \sum_{i=2}^N [V[A'_{i-1}] = 0 \wedge V[A'_i] = 1] \]

\(C[u][v]\) 为相邻对 \((A_{i-1}, A_i) = (u, v)\) 出现的次数。

对于某个循环偏移 \(k\),字符对 \((u, v)\) 构成“01”对的充要条件是:$$V[(u + k) \bmod K] = 0 \quad \text{且} \quad V[(v + k) \bmod K] = 1$$

对于给定的 \(u\)(要求 \(V[(u+k) \bmod K] = 0\)),需要求所有满足 \(V[(v+k) \bmod K] = 1\)\(v\)\(C[u][v]\) 之和。这本质上就是 \(C[u]\)\(V\) 的循环卷积。

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

int main() {
    int n, k, m; ull seed;
    cin >> n >> k >> seed >> m;

    vector<int> b(m), v(k);
    rep(i, m) cin >> b[i];
    rep(i, k) cin >> v[i];
    
    vector<int> c(k*k);
    ull st = seed;
    int a0 = 0, pre = 0;
    rep(i, n) {
        int val = 0;
        if (i < m) val = b[i];
        else {
            ull x = (((st>>18)^st)>>27)%(1ull<<32);
            ull r = st>>59;
            ull y = ((x>>r)+(x<<(32-r)))%(1ull<<32);
            val = y%k;
            st = st*6364136223846793005 + 2026081520260815;
        }
        
        if (i == 0) {
            a0 = val;
            pre = val;
        }
        else {
            c[pre*k+val]++;
            pre = val;
        }
    }

    vector<ll> ans(k);
    rep(i, k) if (v[(a0+i)%k]) ans[i]++;
    
    vector<int> nb(2*k);
    rep(i, 2*k) nb[i] = v[i%k];
    
    vector<int> na(k);
    rep(i, k) {
        bool ok = false;
        rep(j, k) {
            if (c[i*k+j]) {
                ok = true;
                break;
            }
        }
        if (!ok) continue;
        
        rep(ki, k) na[ki] = c[i*k+(k-1-ki)];
        
        auto p = convolution(na, nb);
        
        rep(ki, k) if (!v[(i+ki)%k]) ans[ki] += p[k-1+ki];
    }

    rep(i, k) cout << ans[i] << '\n';
    
    return 0;
}