C. Lock All Doors

我们要先走到“最左边还开着的门”和“最右边还开着的门”那里,所以沿途遇到的所有关着的门都要先开着。然后再从两端开始,依次把门关上就行了!

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

using namespace std;

int main() {
    int n, R;
    cin >> n >> R;
    --R;
    
    vector<int> L(n);
    rep(i, n) cin >> L[i];
    
    int l = R, r = R;
    rep(i, n) {
        if (L[i] == 1) continue;
        l = min(l, i-1);
        r = max(r, i);
    }
    
    int ans = 0;
    for (int i = l+1; i <= r; ++i) {
        ans += L[i]+1;
    }
    
    cout << ans << '\n';
    
    return 0;
}

D. Long Waiting

模拟
用小根堆来维护每批顾客对应的二元组 (离店时间,人数),同时维护一下当前空出来的人数

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

int main() {
    int n, k;
    cin >> n >> k;
    
    priority_queue<P, vector<P>, greater<P>> q;
    int empty = k;
    ll now = 0;
    
    rep(i, n) {
        int a, b, c;
        cin >> a >> b >> c;
        
        if (now < a) {
            while (q.size() and q.top().first < a) {
                empty += q.top().second;
                q.pop();
            }
            now = a;
        }
        
        while (empty < c) {
            now = q.top().first;
            empty += q.top().second;
            q.pop();
        }
        
        empty -= c;
        q.emplace(now+b, c);
        
        cout << now << '\n';
    }
    
    return 0;
}

E. Sum of Subarrays

算贡献
原式就是求 \((A_{L_i}, A_{L_i+1}, \cdots, A_{R_i})\) 的所有连续子段的元素和的总和。也就是说,对于任意 \(x \in [L_i, R_i]\),它被包含的连续子段数等于 \((x-L_i+1) \times (R_i-x+1)\),因此答案就是 \(\sum A_x \times (x-L_i+1) \times (R_i-x+1)\)
\(l = L_i-1, r = R_i+1\)
\((x-L_i+1) \times (R_i-x+1) = (x-l) \times (r-x)\)
\((x-l) \times (r-x)\) 展开得到 \(-x^2 + (l+r)x - lr\)
于是,答案就变成了 \(-\sum x^2 A_x + (l+r)\sum xA_x - lr\sum A_x\)
只需预处理一下 \(A_x, xA_x, x^2A_x\) 的前缀和,就能做到 \(O(1)\) 回答了

代码实现
#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, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector s(3, vector<ll>(n+1));
    rep(k, 3) {
        rep(i, n) {
            ll x = a[i];
            rep(j, k) x *= i;
            s[k][i+1] = s[k][i] + x;
        }
    }
    
    rep(qi, q) {
        int l, r;
        cin >> l >> r;
        --l;
        
        ll ans = 0;
        ans -= (s[2][r]-s[2][l]);
        ans += (s[1][r]-s[1][l])*(l-1+r);
        ans -= (s[0][r]-s[0][l])*(l-1)*r;
        cout << ans << '\n';
    }
    
    return 0;
}

F. Loud Cicada

容斥
对于任意一组蝉,“至少这几只蝉同时出现的次数”就等于它们周期的最小公倍数
对所有子集都这么算一遍,再做快速莫比乌斯变换,就能得到“恰好只有这几只蝉出现的次数”!

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

using namespace std;
using ull = unsigned long long;

const ull INF = 1ull<<60;

int main() {
    int n, m; ull y;
    cin >> n >> m >> y;
    
    vector<ull> a(n);
    rep(i, n) cin >> a[i];
    
    int n2 = 1<<n;
    vector<ull> d(n2);
    
    rep(s, n2) {
        ull x = 1;
        rep(i, n) if (s>>i&1) {
            ull t = a[i]/gcd(x, a[i]);
            if (INF/x < t) x = INF; else x *= t;
        }
        d[s] = y/x;
    }
    
    rep(i, n)rep(s, n2) if (s>>i&1) d[s^1<<i] -= d[s];
    
    ull ans = 0;
    rep(s, n2) if (__builtin_popcount(s) == m) ans += d[s];
    
    cout << ans << '\n';
    
    return 0;
}

G. Small Multiple 2

\(S\) 出现位置以下的各位进行全搜索。由于只需调整最低位就能使其成为 \(K\) 的倍数,所以最低位最多试 \(0∼9\) 即可。
接着,在 \(S\) 出现位置以上的高位取值中,只要找出存在合适低位的最小高位值即可。该“存在合适低位”等价于对应的 \(\bmod K\) 余数落在某个区间,借助
floor_sum 可以高效统计满足条件的高位方案数,因而可通过二分搜索确定最小值。