Solution Set【2024.1.21】
[MtOI2018] 情侣?给我烧了!
设 \(f_{k, n}\) 表示在有 \(n\) 对情侣的情况下钦定 \(k\) 对情侣是和睦的方案数,\(g_{k, n}\) 表示在有 \(n\) 对情侣的情况下恰好有 \(k\) 对情侣是和睦的方案数。考察二者之间的关系,我们有:
根据二项式反演,我们有:
考虑如何求出 \(f_{k, n}\),我们有:
上述式子的含义是:首先从 \(n\) 对情侣中选出 \(k\) 对情侣,然后将这 \(k\) 对情侣钦定为和睦的,然后将这 \(k\) 对情侣的位置确定下来,最后将剩下的 \(2n - 2k\) 个人的位置确定下来。
代入上述关系式,我们有:
取 \(i = m - k\),我们有:
记 \(h_{m} = \sum\limits_{i = 0}^{m} \left(-1\right)^{i} \frac{2^{i}}{i!} \times \left(\frac{1}{\left(m - i\right)!}\right)^2 \times \left(2m - 2i\right)!\),我们有:
设 \(N = \max n\),我们可以在 \(\mathcal{O}(N^2)\) 的时间内预处理出 \(h_{m}\) 的值,然后在 \(\mathcal{O}(1)\) 的时间内求出 \(g_{k, n}\) 的值。
复杂度为 \(\mathcal{O}(N^2 + \sum n)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
namespace MODINT_WITH_FIXED_MOD {
constexpr valueType MOD = 998244353;
template<typename T1, typename T2>
void Inc(T1 &a, T2 b) {
a = a + b;
if (a >= MOD)
a -= MOD;
}
template<typename T1, typename T2>
void Dec(T1 &a, T2 b) {
a = a - b;
if (a < 0)
a += MOD;
}
template<typename T1, typename T2>
T1 sum(T1 a, T2 b) {
return a + b >= MOD ? a + b - MOD : a + b;
}
template<typename T1, typename T2>
T1 sub(T1 a, T2 b) {
return a - b < 0 ? a - b + MOD : a - b;
}
template<typename T1, typename T2>
T1 mul(T1 a, T2 b) {
return (long long) a * b % MOD;
}
template<typename T1, typename T2>
void Mul(T1 &a, T2 b) {
a = (long long) a * b % MOD;
}
template<typename T1, typename T2>
T1 pow(T1 a, T2 b) {
T1 result = 1;
while (b > 0) {
if (b & 1)
Mul(result, a);
Mul(a, a);
b = b >> 1;
}
return result;
}
} // namespace MODINT_WITH_FIXED_MOD
using namespace MODINT_WITH_FIXED_MOD;
class BinomialCoefficient {
private:
valueType N;
ValueVector _Fact, _InvFact;
public:
BinomialCoefficient() = default;
BinomialCoefficient(valueType n) : N(n), _Fact(N + 1, 1), _InvFact(N + 1, 1) {
for (valueType i = 1; i <= N; ++i)
_Fact[i] = mul(_Fact[i - 1], i);
_InvFact[N] = pow(_Fact[N], MOD - 2);
for (valueType i = N - 1; i >= 0; --i)
_InvFact[i] = mul(_InvFact[i + 1], i + 1);
}
valueType operator()(valueType n, valueType m) {
if (n < 0 || m < 0 || n < m)
return 0;
if (m > N)
throw std::out_of_range("BinomialCoefficient::operator() : m > N");
if (n <= N)
return mul(_Fact[n], mul(_InvFact[m], _InvFact[n - m]));
valueType result = 1;
for (valueType i = 0; i < m; ++i)
Mul(result, n - i);
Mul(result, _InvFact[m]);
return result;
}
valueType Fact(valueType n) {
if (n < 0)
return 0;
if (n > N)
throw std::out_of_range("BinomialCoefficient::Fact : n > N");
return _Fact[n];
}
valueType InvFact(valueType n) {
if (n < 0)
return 0;
if (n > N)
throw std::out_of_range("BinomialCoefficient::InvFact : n > N");
return _InvFact[n];
}
};
constexpr valueType V = 1000;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType T;
std::cin >> T;
BinomialCoefficient C(2 * V);
ValueVector H(V + 1);
ValueVector Pow2(V + 1, 0);
Pow2[0] = 1;
for (valueType i = 1; i <= V; ++i)
Pow2[i] = mul(Pow2[i - 1], 2);
for (valueType i = 0; i <= V; ++i) {
for (valueType j = 0; j <= i; ++j) {
if (j & 1)
Dec(H[i], mul(mul(C.InvFact(j), Pow2[j]), mul(mul(C.InvFact(i - j), C.InvFact(i - j)), C.Fact(2 * i - 2 * j))));
else
Inc(H[i], mul(mul(C.InvFact(j), Pow2[j]), mul(mul(C.InvFact(i - j), C.InvFact(i - j)), C.Fact(2 * i - 2 * j))));
}
}
for (valueType testcase = 0; testcase < T; ++testcase) {
valueType N;
std::cin >> N;
ValueVector G(N + 1, 0);
for (valueType i = 0; i <= N; ++i)
G[i] = mul(mul(C.Fact(N), C.Fact(N)), mul(mul(C.InvFact(i), Pow2[i]), H[N - i]));
for (valueType i = 0; i <= N; ++i)
std::cout << G[i] << '\n';
}
std::cout << std::flush;
return 0;
}
[HAOI2015] 按位或
考虑 Min-Max 容斥,我们有:
设全集为 \(U\),我们要求的便是 \(E\left(\max\limits_{x \in U} x\right)\)。那么我们考虑对于每个集合 \(S\) 求出 \(E\left(\min\limits_{x \in S} x\right)\)。
考虑 \(E\left(\min\limits_{x \in S} x\right)\) 的意义,即 \(S\) 中至少有一个元素被选中的期望步数,考虑其中至少一个元素被选中的概率,我们有:
其中 \(P(T)\) 表示 \(T\) 所代表的二进制数出现的概率。
考虑如何计算 \(\sum\limits_{T \cap S \neq \varnothing} P(T)\),正难则反,我们考虑计算 \(\sum\limits_{T \cap S = \varnothing} P(T)\),即 \(S\) 中的元素都没有被选中的概率。发现我们有:
因此对 \(P\) 序列做高维前缀和即可。复杂度为 \(\mathcal{O}(n2^n)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef long double realType;
typedef std::vector<realType> RealVector;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N;
std::cin >> N;
valueType const M = 1 << N;
RealVector P(M);
for (valueType i = 0; i < M; ++i)
std::cin >> P[i];
for (valueType i = 0; i < N; ++i) {
for (valueType j = 0; j < M; ++j) {
if ((j >> i) & 1)
P[j] += P[j ^ (1 << i)];
}
}
realType ans = 0;
for (valueType s = 1; s < M; ++s) {
if (__builtin_popcount(s) & 1) {
ans += static_cast<realType>(1) / static_cast<realType>(1 - P[(M - 1) ^ s]);
} else {
ans -= static_cast<realType>(1) / static_cast<realType>(1 - P[(M - 1) ^ s]);
}
}
if (std::isinf(ans) || std::isnan(ans))
std::cout << "INF" << std::endl;
else
std::cout << std::fixed << std::setprecision(10) << ans << std::endl;
return 0;
}
[国家集训队] 墨墨的等式
考虑任取一个 \(a_i\),设为 \(M\)。那么我们可以对于 \(x \in \left[0, M\right)\) 求出使得 \(b \equiv x \pmod M\) 的最小的合法的 \(b\)。使用同余最短路即可,进而可以计算答案。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
typedef std::priority_queue<ValuePair, std::vector<ValuePair>, std::greater<ValuePair>> PairQueue;
typedef std::vector<bool> bitset;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N, L, R;
std::cin >> N >> L >> R;
ValueVector A(N);
for (auto &a : A)
std::cin >> a;
std::sort(A.begin(), A.end(), std::greater<>());
while (A.back() == 0)
A.pop_back();
std::reverse(A.begin(), A.end());
N = A.size();
if (A.empty()) {
std::cout << 0 << std::endl;
return 0;
}
valueType const M = A.front();
PairMatrix G(M);
ValueVector dist(M, std::numeric_limits<valueType>::max() >> 2);
for (valueType i = 1; i < N; ++i) {
valueType const a = A[i];
for (valueType j = 0; j < M; ++j)
G[j].emplace_back((j + a) % M, a);
}
PairQueue Q;
bitset visited(M, false);
dist[0] = 0;
Q.emplace(0, 0);
while (!Q.empty()) {
auto const [_, x] = Q.top();
Q.pop();
if (visited[x])
continue;
visited[x] = true;
for (auto const &[to, weight] : G[x]) {
if (dist[to] > dist[x] + weight) {
dist[to] = dist[x] + weight;
Q.emplace(dist[to], to);
}
}
}
valueType ans = 0;
dist[0] = M;
for (valueType i = 0; i < M; ++i) {
valueType const count = (R - i) / M - (std::max(L, dist[i]) - i - 1) / M;
if (count > 0)
ans += count;
}
std::cout << ans << std::endl;
return 0;
}
[THUPC 2023 初赛] 背包
观察到有 \(V \ge 10^{11}\),我们考虑其意义。发现一般的背包问题贪心会导致错误的原因是在选择尽可能多的最优物品后不能更好的利用未使用的空间。而在本题中,由于 \(V\) 很大,因此我们可以先在最优的情况下构造出一种方案使得剩余容量是最优物品体积的倍数,然后填充剩余容量即可。
设最优物品的体积为 \(m\),收益为 \(w\),问题就转化为了对于 \(x \in \left[0, m\right)\) 求解 \(f_x\) 代表容量对 \(m\) 取模为 \(x\) 的最大收益。但是发现在转移时可能会出现同时转移到 \(f_x\) 与 \(f_{x + m}\),而后者的状态应当合并到前者中,考虑如何合并。由于最优物品的最优性,我们可以认为在容量为 \(x\) 的基础上,每多使用 \(m\) 的空间就会造成 \(w\) 的损失,进而可以实现转移。对于物品 \(\left(v_i, c_i\right)\) 有转移式:
由于本题建出的图的特殊性,导致了 SPFA 无法通过。因此我们考虑直接使用完全背包来实现转移。
考虑体积模 \(m\) 意义下的完全背包,发现对于体积为 \(v_i\) 的物品,其会形成 \(d = \gcd\left(m, v_i\right)\) 个环,而由于最优物品的最优性,不会存在 \(x\) 转移到 \(x\) 自身的情况,因此该物品在环中最多加入 \(\frac{m}{d} - 1\) 个,我们对于每个环分别做两次转移即可,不难发现这样操作之后可以覆盖环上所有转移边。
复杂度为 \(\mathcal{O}(nv + q)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
constexpr valueType MIN = std::numeric_limits<valueType>::min() >> 1;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N, Q;
std::cin >> N >> Q;
PairVector Items(N);
for (auto &[v, c] : Items)
std::cin >> v >> c;
std::sort(Items.begin(), Items.end(), [](ValuePair const &a, ValuePair const &b) {
return a.second * b.first > b.second * a.first;
});
valueType const M = Items.front().first, W = Items.front().second;
valueType Gcd = M;
for (auto const &[v, c] : Items)
Gcd = std::__gcd(Gcd, v);
ValueVector F(M, MIN);
F[0] = 0;
for (auto const &[v, c] : Items) {
valueType const end = std::__gcd(v, M);
for (valueType start = 0; start < end; ++start) {
valueType x = start, count = 0;
while (count <= 2) {
if (x == start)
++count;
valueType const next = (x + v) % M;
F[next] = std::max(F[next], F[x] + c - (x + v) / M * W);
x = next;
}
}
}
for (valueType q = 0; q < Q; ++q) {
valueType V;
std::cin >> V;
if (V % Gcd != 0) {
std::cout << -1 << '\n';
continue;
}
valueType const remain = V % M;
std::cout << (F[remain] + (V - remain) / M * W) << '\n';
}
std::cout << std::flush;
return 0;
}
ARC084B Small Multiple
设 \(f_x\) 表示模 \(K\) 余 \(x\) 的所有数中的数位和最小的数。考虑如何转移,发现我们需要实现两种操作:个位元素值加 \(1\) 和整体乘 \(10\)。不难发现若个位元素为 \(9\) 那么个位元素加 \(1\) 得到的数位和一定不优,因此无需特殊考虑,可以直接转移,我们有:
同余最短路即可。由于边权 \(w \in \left\{0, 1\right\}\),因此使用 0-1 BFS 即可。复杂度为 \(\mathcal{O}(K)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
typedef std::deque<valueType> ValueDeque;
typedef std::vector<bool> bitset;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType K;
std::cin >> K;
ValueVector F(K, -1);
PairMatrix G(K);
for (valueType i = 0; i < K; ++i) {
G[i].emplace_back((i * 10) % K, 0);
if ((i + 1) % K != (i * 10) % K)
G[i].emplace_back((i + 1) % K, 1);
}
F[1] = 1;
ValueDeque Q;
bitset visited(K, false);
Q.push_back(1);
while (!Q.empty()) {
valueType const x = Q.front();
Q.pop_front();
if (visited[x])
continue;
visited[x] = true;
for (auto const &[to, weight] : G[x]) {
if (F[to] == -1 || F[to] > F[x] + weight) {
F[to] = F[x] + weight;
if (weight == 0 && !visited[to]) {
Q.push_front(to);
} else if (!visited[to]) {
Q.push_back(to);
}
}
}
}
std::cout << F[0] << std::endl;
return 0;
}