AtCoder 杂题集
ABC 453 E - Team Division(1513)
我们要把 \(N\) 个人分成 \(A\)、\(B\) 两个具体的队伍(都不能为空),每个人对“自己所在队伍的总人数”有一个区间限制 \([L_i, R_i]\),求合法的分组方案数。
题解
枚举队伍 A 有 \(i\) 个人时,维护有多少人可以去队伍 A,多少人可以去队伍 B,多少人两者皆可以,多少人两者都不行,然后用组合数统计。维护这个信息需要用到两个差分数组,两个原数组的信息分别表示这个人在哪段区间可以去队伍A、在哪段区间可以两者都去。
view code
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int mod = 998244353;
int norm(int x) {
if (x < 0) {
x += mod;
}
if (x >= mod) {
x -= mod;
}
return x;
}
template <class T>
T power(T a, i64 b) {
T res = 1;
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
struct Z {
int x;
Z(int _x = 0) : x(norm(_x)) {}
Z(i64 _x) : x(norm(_x % mod)) {}
int val() const { return x; }
Z operator-() const { return Z(norm(mod - x)); }
Z inv() const {
assert(x != 0);
return power(*this, mod - 2);
}
Z& operator*=(const Z& rhs) {
x = i64(x) * rhs.x % mod;
return *this;
}
Z& operator+=(const Z& rhs) {
x = norm(x + rhs.x);
return *this;
}
Z& operator-=(const Z& rhs) {
x = norm(x - rhs.x);
return *this;
}
Z& operator/=(const Z& rhs) { return *this *= rhs.inv(); }
friend Z operator*(const Z& lhs, const Z& rhs) {
Z res = lhs;
res *= rhs;
return res;
}
friend Z operator+(const Z& lhs, const Z& rhs) {
Z res = lhs;
res += rhs;
return res;
}
friend Z operator-(const Z& lhs, const Z& rhs) {
Z res = lhs;
res -= rhs;
return res;
}
friend Z operator/(const Z& lhs, const Z& rhs) {
Z res = lhs;
res /= rhs;
return res;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> l(n), r(n);
vector<int> diff(n + 1), diff2(n + 1);
for (int i = 0; i < n; i++) {
cin >> l[i] >> r[i];
diff[l[i]]++;
diff[r[i] + 1]--;
int lo = max(l[i], n - r[i]);
int hi = min(r[i], n - l[i]);
if (lo <= hi) {
diff2[lo] += 1;
diff2[hi + 1] -= 1;
}
}
vector<int> cnt(n + 1), both(n + 1);
for (int i = 1; i <= n; i++) {
cnt[i] = cnt[i - 1] + diff[i];
both[i] = both[i - 1] + diff2[i];
}
vector<Z> fact(n + 1), inv_fact(n + 1);
fact[0] = inv_fact[0] = 1;
for (int i = 1; i <= n; i++) {
fact[i] = fact[i - 1] * i;
inv_fact[i] = 1 / fact[i];
}
auto C = [&](int N, int M) {
return fact[N] * inv_fact[M] * inv_fact[N - M];
};
Z ans = 0;
for (int i = 1; i < n; i++) {
int sa = cnt[i];
int sb = cnt[n - i];
int x = both[i];
int d = n - sa - sb + x;
if (d > 0) continue;
int ca = sa - x;
int need = i - ca;
if (need < 0 || need > x) continue;
ans += C(x, need);
}
cout << ans.val() << '\n';
}
AGC 044 A - Pay to Win(1828)
要把起始为 0 的数变成 N, 可以乘 2, 3, 5 或加减 1, 它们分别需要代价 \(A,B,C,D\). 问最小代价. \(N ≤ 1e18, A,B,C,D ≤ 1e9\).
题解
把整个过程反过来考虑. 注意到在除 \(k\) 前进行 \(+1/-1\) 是不会超过 \(k\) 的整数倍的. 例如, 当前为 31, 那么一定不会加减超过 30 或 32 再除以 2. 这意味着当前为 N 时, 以每次进行除法为划分, 下一次只会转移到 \(\lfloor \frac{N}{k} \rfloor\) 或 \(\lceil \frac{N}{k} \rceil\), \(k = 2, 3, 5\). 进行记忆化搜索就可以解决问题.
我们还要知道会到达多少种状态. 考虑每次除以 2, 3, 5次数一样的情况, 无论向上还是向下取整, 它们的取值至多差 1. 再计算一下\(log_{2}{1e18}\approx 60, log_{3}{1e18}\approx 40 , log_{5}{1e18}\approx 30\). 所以可能到达的状态至多有 \(2*60*40*30\approx 1.5e5\) 种. 这还只是一个粗略的上限, 因为这包含了部分乘积大于 1e18 的情况.
我的代码写得很搓, 这里推荐一波杜老师的代码.
AGC 043 B - 123 Triangle(1945)
题意: \(N × N\) 的上三角型矩阵, 给出第一行的元素, 并且取值都在\([1, 3]\). 其他元素满足\(a_{i,j}=|a_{i-1,j}-a_{i-1,j+1}|\). 问\(a_{N,1}\)的值. \(N ≤ 1e6\).
题解
显然第一行的数都 -1 后不影响答案. 此时所有元素的取值范围都是\([0,2]\). 如果推导式中没有取绝对值的话, 可以直接套二项式系数.
在取绝对值意义下, 奇偶性是不会改变的, 所以我们可以套二项式系数算出\(a_{N,1}\)的奇偶. 如果是奇数, 结果就是 1. 如果是偶数, 需要考虑结果是 0 还是 2. 如果第一行的元素中有 1, 那么结果一定不会是 2. 如果没有 1, 那么对整个矩阵除以 2, 对新矩阵再套一次二项式系数, 最后结果乘以 2 即可.

浙公网安备 33010602011771号