Solution Set【2024.1.11】
A. 战争模拟器
设 \(f_{l, r, p}\) 表示区间满足 \(\operatorname{argmax}\limits_{l \le i \le r} A_i = p\) 的情况下区间 \(\left[l, r\right]\) 的最大利益,有转移:
注意到若对于 \(p, q\) 满足 \(l \le p, q \le r\) 且 \(A_{q} < A_{p}\),那么一定有 \(f_{l, r, q} < f_{l, r, p}\)。即可以最优方案中的 \(p\) 一定是最大值,因此我们可以考虑设 \(f_{l, r}\) 表示区间 \(\left[l, r\right]\) 的最大利益,转移时枚举最大值,有:
发现转移式的后半部分可以写为斜率优化的形式,进而可以对于每个 \(p\) 建出凸包。
若直接在凸包上二分那么复杂度为 \(\mathcal{O}(n^3\sum\log k)\),无法接受。
考虑通过改变转移顺序使得在凸包上查询的斜率递增,发现可以先逆序枚举 \(l\),后正序枚举 \(p\),最后正序枚举 \(r\)。时间复杂度为 \(\mathcal{O}(n^3 + n \sum k + \sum k \log 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 __int128_t MultiType;
constexpr valueType MIN = std::numeric_limits<valueType>::min(), MAX = std::numeric_limits<valueType>::max();
bool Compare(ValuePair const &A, ValuePair const &B, ValuePair const &C, ValuePair const &D) {
assert(B.first - A.first > 0);
assert(D.first - C.first > 0);
return (__int128_t) (B.second - A.second) * (__int128_t) (D.first - C.first) >= (__int128_t) (D.second - C.second) * (__int128_t) (B.first - A.first);
}
bool Compare(ValuePair const &A, ValuePair const &B, ValuePair const &C) {
return Compare(A, B, B, C);
}
bool Compare(ValuePair const &A, ValuePair const &B, valueType K) {
assert(B.first - A.first > 0);
return (__int128_t) (B.second - A.second) <= (__int128_t) (K) * (__int128_t) (B.first - A.first);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("war.in", "r", stdin);
freopen("war.out", "w", stdout);
#endif
valueType N;
std::cin >> N;
PairMatrix A(N + 1);
ValueMatrix Q(N + 1, ValueVector(N + 1, 0));
for (valueType i = 1; i <= N; ++i) {
for (valueType j = i; j <= N; ++j)
std::cin >> Q[i][j];
}
for (valueType i = 1; i <= N; ++i) {
for (valueType j = 1; j <= N; ++j)
Q[i][j] += Q[i - 1][j];
}
for (valueType i = 1; i <= N; ++i) {
for (valueType j = 1; j <= N; ++j)
Q[i][j] += Q[i][j - 1];
}
auto GetSum = [&](valueType l1, valueType l2, valueType r1, valueType r2) -> valueType {// get sum of Q[l][r] for l in [l1, l2] and r in [r1, r2]
return Q[l2][r2] - Q[l1 - 1][r2] - Q[l2][r1 - 1] + Q[l1 - 1][r1 - 1];
};
for (valueType i = 1; i <= N; ++i) {
valueType K;
std::cin >> K;
PairVector B(K);
for (auto &[v, c] : B)
std::cin >> v >> c;
std::sort(B.begin(), B.end());
A[i].reserve(K);
for (auto const &iter : B) {
auto const &[v, c] = iter;
while (A[i].size() > 1 && (A[i].back().first == v || Compare(A[i][A[i].size() - 2], A[i].back(), iter)))
A[i].pop_back();
A[i].push_back(iter);
}
}
ValueMatrix F(N + 2, ValueVector(N + 2, 0));
for (valueType l = 1; l <= N; ++l)
for (valueType r = l; r <= N; ++r)
F[l][r] = MIN;
for (valueType l = N; l >= 1; --l) {
for (valueType p = l; p <= N; ++p) {
valueType pointer = 0;
for (valueType r = p; r <= N; ++r) {
valueType const K = GetSum(l, p, p, r);
while (pointer + 1 < A[p].size() && Compare(A[p][pointer], A[p][pointer + 1], K))
++pointer;
auto const &[x, y] = A[p][pointer];
F[l][r] = std::max(F[l][r], F[l][p - 1] + F[p + 1][r] - (y - x * K));
}
}
}
std::cout << F[1][N] << std::endl;
return 0;
}
B. 种树
首先考虑如何计算根链期望长度 \(\operatorname{depth}_u\),不难发现通过枚举其父亲节点可以得到转移式:
其中 \(s_i = \sum\limits_{j \le i} a_j\),即 \(a\) 的前缀和。不难发现通过维护 \(\sum\limits_{p < u}{a_p} \times \left(\operatorname{depth}_p + c_p\right)\) 的值可以在 \(\mathcal{O}(1)\) 的时间内计算出 \(\operatorname{depth}_u\)。进而我们可以在 \(\mathcal{O}(n)\) 的时间内计算出所有节点的 \(\operatorname{depth}\)。
考虑如何计算点对 \(u, v\) 间的期望距离,不难发现可以通过枚举其最近公共祖先 \(x\) 得到转移式(假设 \(u < v\)):
首先考虑如何计算 \(\operatorname{P}(u)\),发现其等同于 \(u\) 在 \(v\) 的根链上的概率,不妨假设路径 \(u \rightarrow v\) 上的节点依次为 \(k_1, k_2,\cdots, k_l\),那么这条路径出现的概率为:
可以发现 \(\operatorname{P}(u)\) 即为 \(u, v\) 之间所有可能的路径的概率之和,因此有:
继续考虑对于 \(x < u\),如何计算 \(\operatorname{P}(x)\),不妨将点分为两部分,第一部分为标号在 \(\left(x, u\right)\) 之间的点,第二部分为标号在 \(\left(u, v\right)\) 之间的点。
对于第一部分的点,其可能的出现情况为:
- 不在路径中出现
- 仅在路径 \(x \rightarrow v\) 中出现
- 仅在路径 \(x \rightarrow u\) 中出现
对于第二部分的点,其可能的出现情况为:
- 不在路径中出现
- 仅在路径 \(x \rightarrow v\) 中出现
因此有:
可以发现该式仅与 \(u\) 有关,因此可以快速计算。
总复杂度为 \(\mathcal{O}(n \times f(p) + q)\),其中 \(f(p)\) 表示计算某数在模 \(p\) 意义下的逆元的复杂度。
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;
namespace MODINT_WITH_FIXED_MOD {
constexpr valueType MOD = 1e9 + 7;
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;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
#endif
valueType N, Q;
std::cin >> N >> Q;
ValueVector A(N + 1, 0);
ValueVector C(N + 1, 0);
ValueVector S(N + 1, 0); // sum of A
ValueVector InvS(N + 1, 0);// inverse of S
ValueVector D(N + 1, 0); // depth to root
ValueVector L(N + 1, 0); // lca depth
for (valueType i = 1; i < N; ++i)
std::cin >> A[i];
for (valueType i = 1; i <= N; ++i)
std::cin >> C[i];
std::partial_sum(A.begin(), A.end(), S.begin());
InvS = S;
for (valueType i = 0; i <= N; ++i)
InvS[i] = pow(S[i], MOD - 2);
valueType WeightedSum = mul(A[1], sum(C[1], D[1]));
for (valueType i = 2; i <= N; ++i) {
D[i] = sum(mul(WeightedSum, InvS[i - 1]), C[i]);
Inc(WeightedSum, mul(A[i], sum(C[i], D[i])));
}
valueType Sum = 0;
for (valueType i = 2; i <= N; ++i) {
L[i] = mul(Sum, mul(InvS[i - 1], InvS[i]));
Mul(Sum, mul(sum(2 * A[i], S[i - 1]), InvS[i - 1]));
Inc(Sum, mul(D[i], A[i] * A[i]));
}
for (valueType q = 0; q < Q; ++q) {
valueType u, v;
std::cin >> u >> v;
if (u == v) {
std::cout << 0 << '\n';
continue;
}
if (u > v)
std::swap(u, v);
valueType ans = sum(D[u], D[v]);
Dec(ans, mul(2, L[u]));
Dec(ans, mul(mul(2, D[u]), mul(A[u], InvS[u])));
std::cout << ans << '\n';
}
std::cout << std::flush;
return 0;
}