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\) 的个数
令 \(k=i-j\),那么 \(i=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)\) 的系数
浙公网安备 33010602011771号