C. Candy Tribulation

不妨先只分配给每个孩子 \(A_i\) 个大糖果,然后通过不断减去 \(Y-X\) 来分配对应的小糖果个数从而得到目标值,那么分配到的大糖果数就是 \(A_i\) 减去小糖果个数即可
不难发现,\(\min(A) \times y\) 就是我们需要的最佳目标值,即 \(A_i\) 最小的那个孩子不需要给他分配小糖果
注意需要特判无解的情况

代码实现
#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; ll x, y;
    cin >> n >> x >> y;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    sort(a.begin(), a.end());
    
    ll ans = 0;
    ll w = a[0]*y;
    rep(i, n) {
        ll sw = a[i]*y;
        ll dif = sw-w;
        if (dif%(y-x) != 0) {
            puts("-1");
            return 0;
        }
        ll num = dif/(y-x);
        if (num > a[i]) {
            puts("-1");
            return 0;
        }
        ans += a[i]-num;
    }
    
    cout << ans << '\n';
    
    return 0;
}

D. Suddenly, A Tempest

把初始的黑色区域看成一个轴对齐的矩形 \([0,X)\times[0,Y)\)。每次风暴把当前的若干矩形“映射”到新的若干矩形(必要时按 \(x=A_i\)\(y=A_i\) 切分再对其中每一部分作平移),把这些矩形作为新的黑色区域。所有风暴做完后得到若干矩形(它们只会相互接触,不会“重叠面积”),用并查集合并那些相连(重叠/共边)的矩形,最后每个并查集对应一个连通分量,连通分量的格子数就是该组中各矩形面积之和。按升序输出。

代码实现
#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 Rect {
    ll lx, rx, ly, ry;
};

int main() {
    int n, x, y;
    cin >> n >> x >> y;
    
    vector<Rect> rects;
    rects.emplace_back(0, x, 0, y);
    
    rep(i, n) {
        vector<Rect> old;
        swap(rects, old);
        
        char c; int a, b;
        cin >> c >> a >> b;
        
        for (Rect e : old) {
            if (c == 'X') {
                if (e.lx < a and a < e.rx) {
                    rects.emplace_back(e.lx, a, e.ly-b, e.ry-b);
                    rects.emplace_back(a, e.rx, e.ly+b, e.ry+b);
                }
                else if (e.rx <= a) {
                    rects.emplace_back(e.lx, e.rx, e.ly-b, e.ry-b);
                }
                else {
                    rects.emplace_back(e.lx, e.rx, e.ly+b, e.ry+b);
                }
            }
            else {
                if (e.ly < a and a < e.ry) {
                    rects.emplace_back(e.lx-b, e.rx-b, e.ly, a);
                    rects.emplace_back(e.lx+b, e.rx+b, a, e.ry);
                }
                else if (e.ry <= a) {
                    rects.emplace_back(e.lx-b, e.rx-b, e.ly, e.ry);
                }
                else {
                    rects.emplace_back(e.lx+b, e.rx+b, e.ly, e.ry);
                }
            }
        }
    }
    
    int m = rects.size();
    dsu uf(m);
    rep(i, m)rep(j, i) {
        Rect a = rects[i];
        Rect b = rects[j];
        ll cx = min(a.rx, b.rx) - max(a.lx, b.lx);
        ll cy = min(a.ry, b.ry) - max(a.ly, b.ly);
        if (cx < 0 or cy < 0) continue;
        if (cx or cy) uf.merge(i, j);
    }
    
    vector<ll> areas(m);
    rep(i, m) {
        Rect a = rects[i];
        areas[uf.leader(i)] += (a.rx-a.lx)*(a.ry-a.ly);
    }
    
    sort(areas.begin(), areas.end(), greater<>());
    while (areas.back() == 0) areas.pop_back();
    reverse(areas.begin(), areas.end());
    cout << areas.size() << '\n';
    for (ll a : areas) cout << a << ' ';
    
    return 0;
}

E. Clamp

讨论取值落在 \([0, l)\)\([l, r)\) 以及 \([r, \infty)\) 这三段区间的贡献
只需开两个树状数组来维护个数和总和即可

代码实现
#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, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    const int MX = 500005;
    fenwick_tree<ll> tc(MX), ts(MX);
    rep(i, n) tc.add(a[i], 1), ts.add(a[i], a[i]);
    
    rep(qi, q) {
        int type, x, y;
        cin >> type >> x >> y;
        if (type == 1) {
            --x;
            tc.add(a[x], -1); ts.add(a[x], -a[x]);
            a[x] = y;
            tc.add(a[x], 1); ts.add(a[x], a[x]);
        }
        else {
            ll ans = 0;
            if (x > y) {
                ans = (ll)n*x;
            }
            else {
                ans += tc.sum(0, x)*x;
                ans += ts.sum(x, y);
                ans += tc.sum(y, MX)*y;
            }
            cout << ans << '\n';
        }
    }
    
    return 0;
}

F. Candy Redistribution

把所有孩子最终要到的数记为平均数 T = sum(A)/N(如果 sum(A) 不能被 N 整除则无解)。把每个孩子 i 的“差额”定义为 a[i] -= T,于是这些差额的和为 0。正的差额表示多余(要送出),负的差额表示缺少(要收到)。

\(N\) 个孩子看成顶点,允许的操作是把任意正整数从一个顶点送到另一个顶点。为了让所有差额变为 \(0\),需要把“正数”逐步送给“负数”。每一次把 z\(x→y\) ,在图论上等价于在两个顶点之间连一条有权边(这些边的总数就是操作数)。把某一组顶点内部完全平衡(它们差额之和为 \(0\)),可以用该组内部的 \(|\text{group}|-1\) 次操作把它们互相调平(把它们连成一条链,沿链把富的把多余转给下一个,最后链上每个点都达到了 \(0\))。因此:

  • 若你能把全体顶点划分成 k互不相连(互不相交)的子集,每个子集内差额和为 \(0\),则可以把每个子集内部独立地用 \(|\text{subset}|-1\) 次操作平衡,总代价为 \(\sum (|\text{subset}|-1) = N - k\) 次操作。

  • 这给出下界:任何可行方案至少需要 \(N - k_{\max}\) 次操作,其中 \(k_{\max}\) 是能把所有点划分为的最多个“和为 \(0\) 的子集”的个数。并且上述链式内部平衡方法能达到这个下界(构造法),因此最小操作数就是 \(N - k_{\max}\)

所以问题等价于:在差额数组上,把点划分为尽可能多的、每组和为 0 的不相交子集,求这个最大 \(k_{\max}\),并给出对应的具体转移序列(转移数为 \(N - k_{\max}\))。

  • dp[S] 表示:在集合 S 中,能选出的互不相交的和为 0 的子集的最大个数
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;

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

int main() {
    int n;
    cin >> n;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    {
        int sum = 0;
        rep(i, n) sum += a[i];
        if (sum%n) {
            puts("-1");
            return 0;
        }
        sum /= n;
        rep(i, n) a[i] -= sum;
    }
    
    int n2 = 1<<n;
    vector<int> sums(n2);
    rep(s, n2)rep(i, n) if (s>>i&1) sums[s] += a[i];
    
    vector<int> dp(n2);
    rep(s, n2) {
        if (sums[s] == 0) dp[s]++;
        rep(i, n) if (~s>>i&1) {
            chmax(dp[s|1<<i], dp[s]); 
        }
    }
    
    vector<int> p;
    {
        int s = n2-1;
        while (s) {
            if (sums[s] == 0) dp[s]--;
            rep(i, n) if (s>>i&1) {
                if (dp[s] == dp[s^1<<i]) {
                    s ^= 1<<i;
                    p.push_back(i);
                    break;
                }
            }
        }
    }
    
    vector<tuple<int, int, int>> lefts, rights;
    {
        int sum = 0;
        rep(i, n-1) {
            sum += a[p[i]];
            if (sum > 0) rights.emplace_back(p[i], p[i+1], sum);
            if (sum < 0) lefts.emplace_back(p[i+1], p[i], -sum);
        }
    }
    
    auto ans = rights;
    reverse(lefts.begin(), lefts.end());
    ans.insert(ans.end(), lefts.begin(), lefts.end());
    
    cout << ans.size() << '\n';
    for (auto [s, t, c] : ans) cout << s+1 << ' ' << t+1 << ' ' << c << '\n';
    
    return 0;
}

G. Sum of Binom(A, B)

考虑元素分布,记 \(a_x\)\(A_i = x\) 的个数,\(b_x\)\(A_i = x\) 的个数

\[\sum_{i=1}^N\sum_{j=1}^M \binom{A_i}{B_j} = \sum_{j \leqslant i} \binom{i}{j} \cdot a_i \cdot b_j = \sum_{j \leqslant i} \frac{i!}{j!(i-j)!} \cdot a_i \cdot b_j \]

\(k=i-j\),那么 \(i=j+k\)

\[\sum_{j \leqslant i} \frac{i!}{j!(i-j)!} \cdot a_i \cdot b_j = \sum \frac{(j+k)!}{j!k!} \cdot a_{j+k} \cdot b_j = \sum (\frac{1}{j!}b_j) \cdot (\frac{1}{k!}) \cdot ((j+k)!a_{j+k}) \]

\(c_i = \sum\limits_{j+k=i} (\frac{1}{j!}b_j) \cdot (\frac{1}{k!})\)

于是,原式 \(= \sum\limits_i c_i \cdot (i!a_i)\)

跑一遍 \(\text{NTT}\) 求出 \(c_i\) 即可

代码实现
#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, m;
    cin >> n >> m;
    
    const int M = 500005;
    vector<int> a(M), b(M);
    rep(i, n) {
        int x;
        cin >> x;
        a[x]++;
    }
    rep(i, m) {
        int x;
        cin >> x;
        b[x]++;
    }
    
    vector<mint> x(M), y(M);
    rep(i, M) x[i] = ifacts(i)*b[i];
    rep(i, M) y[i] = ifacts(i);
    auto c = convolution(x, y);
    mint ans;
    rep(i, M) ans += c[i]*facts(i)*a[i];
    cout << ans.val() << '\n';
    
    return 0;
}

还有一种更直接的做法

考虑生成函数,原式可以写成 \(\sum\limits_{i=1}^N\sum\limits_{j=1}^M [x^{B_j}](1+x)^{A_i}\)

\(f(x) = \sum\limits_{i=1}^N (1+x)^{A_i}\)

于是原式就变成了 \(\sum\limits_{j=1}^M [x^{B_j}]f(x)\)

再记 \(g(x) = \sum\limits_{i=1}^N x^{A_i}\),则有 \(f(x)=g(x+1)\),然后套 Polynomial Taylor Shift 这个板子就能求出 \(f(x)\) 的系数