Solution Set【2024.2.15】
A. 枇杷树
考虑从增量的角度计算答案,若我们在构造树 \(T_n\) 时已经得到了树 \(T_x\) 和 树 \(T_y\) 的答案 \(Ans_x\) 和 \(Ans_y\),我们考虑如何得出 \(Ans_n\)。
考虑 \(Ans_n\) 与 \(Ans_x + Ans_y\),发现即为跨越新增的边的所有路径权值之和。其可以表达为:
其中 \(f(n, p) = \sum\limits_{0 \le i < size_n} \operatorname{dist}(p, i)\) 即在 \(T_n\) 中 \(p\) 与所有节点的距离之和。考虑如何求出 \(f(n, p)\),不妨通过递归求出,考虑 \(p\) 在哪棵树中,那么另一棵子树的贡献可以拆分为连接点到其所有节点的距离之和再加上连接点到 \(p\) 的距离即可,不妨设 \(p\) 在 \(T_x\) 中,那么我们有:
其中 \(g(n, p, q)\) 表示在 \(T_n\) 中 \(p, q\) 两点间的距离。求解其也可以通过讨论其在构造树 \(T_n\) 前属于的两棵子树进行递归求解。
我们下面尝试论证其复杂度,我们设在构造树的过程中连接其他树的节点为关键点,不难发现除去所有关键点之间的状态外,每次求解 \(f, g\) 使用到的状态数均为 \(\mathcal{O}(m)\),而所有关键点之间的状态数为 \(\mathcal{O}(m^2)\)。而我们需要求解 \(f, g\) 共 \(\mathcal{O}(m)\) 次。因此复杂度为 \(\mathcal{O}(m^3)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::map<ValuePair, valueType> ValueMap;
typedef std::vector<ValueMap> MapMatrix;
typedef std::tuple<valueType, valueType, valueType, valueType, valueType> ValueTuple;
typedef std::vector<ValueTuple> TupleVector;
namespace MODINT {
constexpr valueType MOD = 1e9 + 7;
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>
void Inc(T1 &a, T2 b) {
a += b;
if (a >= MOD)
a -= MOD;
}
template<typename T1, typename T2>
void Dec(T1 &a, T2 b) {
a -= b;
if (a < 0)
a += MOD;
}
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;
}
} // namespace MODINT
using namespace MODINT;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("loquat.in", "r", stdin);
freopen("loquat.out", "w", stdout);
#endif
valueType M;
std::cin >> M;
ValueVector Size(M + 1, 0);
TupleVector Tree(M + 1);
ValueVector Ans(M + 1, 0);
Size[0] = 1;
for (valueType i = 1; i <= M; ++i) {
auto &[x, y, u, v, w] = Tree[i];
std::cin >> x >> y >> u >> v >> w;
Size[i] = Size[x] + Size[y];
}
ValueMap MemoryF;
MapMatrix MemoryG(M + 1);
MemoryF[std::make_pair(0, 0)] = 0;
MemoryG[0][std::make_pair(0, 0)] = 0;
auto G = [&](auto self, valueType n, valueType x, valueType y) -> valueType {
if (x > y)
std::swap(x, y);
if (MemoryG[n].count(std::make_pair(x, y)))
return MemoryG[n][std::make_pair(x, y)];
valueType &result = MemoryG[n][std::make_pair(x, y)];
auto const [l, r, u, v, w] = Tree[n];
if (y < Size[l]) { // both in tree l
Inc(result, self(self, l, x, y));
} else if (x >= Size[l]) { // both in tree r
Inc(result, self(self, r, x - Size[l], y - Size[l]));
} else {
Inc(result, self(self, l, x, u));
Inc(result, w);
Inc(result, self(self, r, y - Size[l], v));
}
return result;
};
auto F = [&](auto self, valueType n, valueType x) -> valueType {
if (MemoryF.count(std::make_pair(n, x)))
return MemoryF[std::make_pair(n, x)];
valueType &result = MemoryF[std::make_pair(n, x)];
auto const [l, r, u, v, w] = Tree[n];
if (x >= Size[l]) { // in tree r
Inc(result, self(self, l, u));
Inc(result, mul(G(G, n, u, x), Size[l] % MOD));
Inc(result, self(self, r, x - Size[l]));
} else { // in tree l
Inc(result, self(self, r, v));
Inc(result, mul(G(G, n, v + Size[l], x), Size[r] % MOD));
Inc(result, self(self, l, x));
}
return result;
};
for (valueType i = 1; i <= M; ++i) {
auto const &[x, y, u, v, w] = Tree[i];
Ans[i] = sum(Ans[x], Ans[y]);
Inc(Ans[i], mul(F(F, x, u), Size[y] % MOD));
Inc(Ans[i], mul(F(F, y, v), Size[x] % MOD));
Inc(Ans[i], mul(w, mul(Size[x] % MOD, Size[y] % MOD)));
}
for (valueType i = 1; i <= M; ++i)
std::cout << Ans[i] << std::endl;
return 0;
}
B. 上古遗迹
对于 \(i\),设 \(\left[a_i, b_i\right]\) 为极长的区间满足其最小值为 \(h_i\)。
那么我们要求解的为:
我们考虑通过讨论 \(l, r\) 与 \(a, b\) 的关系求解。发现其实际上只有如下四种关系:
- \(l \le a \le b \le r\)。
- \(a \le l \le r \le b\)。
- \(a \le l \le b \le r\)。
- \(l \le a \le r \le b\)。
前两种可以通过对右端点进行扫描线,然后使用线段树维护左端点答案即可求解,不多赘述。而第四种可以通过将序列和询问均翻转进而得到第三种情况,因此下文主要讨论如何求解第三种情况。
发现此时答案为:
发现其可以表达为关于 \(l\) 一次函数的形式,进而我们的问题转化为了,对于每个询问在所有满足 \(a_i \le l \le b_i\) 且 \(b_i \le r\) 的一次函数中寻找在位置 \(l\) 的最小值即可。
我们考虑通过扫描线来处理第二个限制,而第一个限制实际上为一次函数的定义域限制,通过李超线段树即可解决。
复杂度为 \(\mathcal{O}(n\log n^2)\)。
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::stack<valueType> ValueStack;
typedef std::tuple<valueType, valueType, valueType, valueType> ValueTuple;
typedef std::vector<ValueTuple> TupleVector;
constexpr valueType MIN = std::numeric_limits<valueType>::min() >> 1;
class Tree {
private:
valueType N;
ValueVector data;
void Merge(valueType id) {
data[id] = std::max(data[id << 1], data[id << 1 | 1]);
}
public:
Tree() = default;
Tree(valueType n) : N(n), data(4 * N + 10, -1) {}
public:
void Modify(valueType x, valueType v) {
Modify(1, 1, N, x, v);
}
valueType Query(valueType l, valueType r) {
return Query(1, 1, N, l, r);
}
private:
void Modify(valueType id, valueType nodeL, valueType nodeR, valueType x, valueType v) {
if (nodeL == nodeR) {
data[id] = std::max(data[id], v);
return;
}
valueType const mid = (nodeL + nodeR) >> 1;
if (x <= mid)
Modify(id << 1, nodeL, mid, x, v);
else
Modify(id << 1 | 1, mid + 1, nodeR, x, v);
Merge(id);
}
valueType Query(valueType id, valueType nodeL, valueType nodeR, valueType queryL, valueType queryR) const {
if (queryL <= nodeL && nodeR <= queryR)
return data[id];
valueType const mid = (nodeL + nodeR) >> 1;
if (queryR <= mid)
return Query(id << 1, nodeL, mid, queryL, queryR);
else if (queryL > mid)
return Query(id << 1 | 1, mid + 1, nodeR, queryL, queryR);
else
return std::max(Query(id << 1, nodeL, mid, queryL, queryR), Query(id << 1 | 1, mid + 1, nodeR, queryL, queryR));
}
};
class LiChaoTree {
private:
struct Line {
valueType K, B;
Line() : K(0), B(MIN){};
Line(valueType k, valueType b) : K(k), B(b) {}
valueType operator()(valueType x) const {
return K * x + B;
}
};
private:
struct Node {
valueType leftBound, rightBound, mid;
valueType leftSon, rightSon;
Line data;
Node() : leftBound(0), rightBound(-1), mid(-1), leftSon(-1), rightSon(-1), data() {}
Node(valueType l, valueType r) : leftBound(l), rightBound(r), mid((l + r) >> 1), leftSon(-1), rightSon(-1), data() {}
Node(valueType l, valueType r, Line line) : leftBound(l), rightBound(r), mid((l + r) >> 1), leftSon(-1), rightSon(-1), data(line) {}
};
private:
valueType N, PoolSize;
valueType root;
std::vector<Node> Tree;
valueType Allocate() {
if (PoolSize + 1 >= Tree.size())
Tree.resize(Tree.size() << 1);
return ++PoolSize;
}
public:
LiChaoTree() : N(0), PoolSize(0), root(-1), Tree() {}
LiChaoTree(valueType n) : N(n), PoolSize(0), root(-1), Tree(4 * N + 10) {}
public:
void Push(valueType l, valueType r, valueType k, valueType b) {
Push(root, 1, N, l, r, Line(k, b));
}
valueType Query(valueType x) {
return Query(root, 1, N, x);
}
private:
void Push(valueType &id, valueType l, valueType r, Line Next) {
if (id == -1) {
id = Allocate();
Tree[id] = Node(l, r, Next);
}
auto &Prev = Tree[id].data;
valueType const mid = Tree[id].mid;
if (Next(mid) > Prev(mid))
std::swap(Next, Prev);
if (Next(l) > Prev(l))
Push(Tree[id].leftSon, l, mid, Next);
else if (Next(r) > Prev(r))
Push(Tree[id].rightSon, mid + 1, r, Next);
}
void Push(valueType &id, valueType nodeL, valueType nodeR, valueType queryL, valueType queryR, Line const &line) {
if (id == -1) {
id = Allocate();
Tree[id] = Node(nodeL, nodeR);
}
if (queryL <= nodeL && nodeR <= queryR) {
Push(id, nodeL, nodeR, line);
return;
}
valueType const mid = (nodeL + nodeR) >> 1;
if (queryL <= mid)
Push(Tree[id].leftSon, nodeL, mid, queryL, queryR, line);
if (queryR > mid)
Push(Tree[id].rightSon, mid + 1, nodeR, queryL, queryR, line);
}
valueType Query(valueType id, valueType nodeL, valueType nodeR, valueType x) {
if (id == -1)
return MIN;
if (nodeL == nodeR)
return Tree[id].data(x);
valueType const mid = (nodeL + nodeR) >> 1;
if (x <= mid)
return std::max(Tree[id].data(x), Query(Tree[id].leftSon, nodeL, mid, x));
else
return std::max(Tree[id].data(x), Query(Tree[id].rightSon, mid + 1, nodeR, x));
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("relics.in", "r", stdin);
freopen("relics.out", "w", stdout);
#endif
valueType N, M;
std::cin >> N >> M;
ValueVector A(N + 1);
for (valueType i = 1; i <= N; ++i)
std::cin >> A[i];
TupleVector B;
B.reserve(N);
{
ValueVector L(N + 1, N + 1), R(N + 1, 0);
ValueStack stack;
for (valueType i = 1; i <= N; ++i) {
while (!stack.empty() && A[stack.top()] > A[i]) {
R[stack.top()] = i;
stack.pop();
}
stack.push(i);
}
while (!stack.empty()) {
R[stack.top()] = N + 1;
stack.pop();
}
for (valueType i = N; i >= 1; --i) {
while (!stack.empty() && A[stack.top()] > A[i]) {
L[stack.top()] = i;
stack.pop();
}
stack.push(i);
}
while (!stack.empty()) {
L[stack.top()] = 0;
stack.pop();
}
for (valueType i = 1; i <= N; ++i)
B.emplace_back(i, L[i] + 1, R[i] - 1, (R[i] - L[i] - 1) * A[i]);
}
ValueVector Ans(M);
PairVector Querys(M);
for (auto &[l, r] : Querys)
std::cin >> l >> r;
{ // l <= a <= b <= r
Tree tree(N);
PairMatrix Nodes(N + 1);
for (auto const &[i, l, r, w] : B)
Nodes[r].emplace_back(l, w);
PairMatrix Queue(N + 1);
for (valueType i = 0; i < M; ++i) {
auto const &[l, r] = Querys[i];
Queue[r].emplace_back(l, i);
}
for (valueType r = 1; r <= N; ++r) {
for (auto const &[l, w] : Nodes[r])
tree.Modify(l, w);
for (auto const &[l, id] : Queue[r])
Ans[id] = std::max(Ans[id], tree.Query(l, r));
}
}
{ // a <= l <= r <= b
Tree tree(N);
PairMatrix Nodes(N + 1);
for (auto const &[i, l, r, w] : B)
Nodes[r].emplace_back(l, A[i]);
PairMatrix Queue(N + 1);
for (valueType i = 0; i < M; ++i) {
auto const &[l, r] = Querys[i];
Queue[r].emplace_back(l, i);
}
for (valueType r = N; r >= 1; --r) {
for (auto const &[l, w] : Nodes[r])
tree.Modify(l, w);
for (auto const &[l, id] : Queue[r])
Ans[id] = std::max(Ans[id], tree.Query(1, l) * (r - l + 1));
}
}
{ // a <= l <= b <= r
LiChaoTree tree(N);
ValueMatrix Nodes(N + 1);
for (valueType id = 0; id < N; ++id) {
auto const &[i, l, r, w] = B[id];
Nodes[r].push_back(id);
}
PairMatrix Queue(N + 1);
for (valueType i = 0; i < M; ++i) {
auto const &[l, r] = Querys[i];
Queue[r].emplace_back(l, i);
}
for (valueType r = 1; r <= N; ++r) {
for (auto const &id : Nodes[r]) {
auto const &[i, l, r, w] = B[id];
tree.Push(l, r, -A[i], (r + 1) * A[i]);
}
for (auto const &[l, id] : Queue[r])
Ans[id] = std::max(Ans[id], tree.Query(l));
}
}
{ // l <= a <= r <= b
LiChaoTree tree(N);
ValueMatrix Nodes(N + 1);
for (valueType id = 0; id < N; ++id) {
auto const &[i, l, r, w] = B[id];
Nodes[l].push_back(id);
}
PairMatrix Queue(N + 1);
for (valueType i = 0; i < M; ++i) {
auto const &[l, r] = Querys[i];
Queue[l].emplace_back(r, i);
}
for (valueType l = N; l >= 1; --l) {
for (auto const &id : Nodes[l]) {
auto const &[i, l, r, w] = B[id];
tree.Push(l, r, A[i], -(l - 1) * A[i]);
}
for (auto const &[r, id] : Queue[l])
Ans[id] = std::max(Ans[id], tree.Query(r));
}
}
for (auto const &x : Ans)
std::cout << x << '\n';
std::cout << std::flush;
return 0;
}
C. 吞天得手
考虑如下自动机:每个节点代表了一个本质不同的子序列。从起始状态到达某个节点的转移边字符顺次连接即可得到该节点所代表的子序列。
那么我们只需要在该自动机上遍历出字典序前 \(k\) 小的路径即可。
考虑如何建出自动机,不妨将其转化为对于某个节点,找到其出边集合以及出边可以到达的节点所代表的子序列在原序列中的出现次数。因此我们可以将转移边视作对现有序列的扩展。
设我们现在已经得到了一种子序列 \(s\),为了按字典序遍历子序列,我们可以从小到大枚举其末尾新增的元素。
考虑在确定新增元素后如何计算方案数。发现对于新增元素在原序列的每次出现的位置,其贡献系数取决于在其之前有多少个完整出现的子序列 \(s\)。因此这启示我们对于每种子序列维护其末尾元素的出现位置集合和每个位置对应的方案数。进而设当前位置集合中最早的位置 \(x\),那么 \(a[x + 1, n]\) 中的元素均可在扩展在当前子序列 \(s\) 末尾。不妨从小到大枚举其,设当前枚举到的元素为 \(v\),那么扩展后的状态的位置集合即为 \(v\) 在 \(a[x + 1, n]\) 中的所有出现位置组成的集合。而新的位置集合每个位置对应的方案数为原来子序列 \(s\) 对应的位置集合中所有比其出现早的位置的方案数之和。
在遍历自动机的同时维护子序列哈希值即可。复杂度为 \(\mathcal{O}(n\log n + k \log n)\)。
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::tuple<ValuePair, valueType, valueType> DataTuple;
typedef std::priority_queue<DataTuple, std::vector<DataTuple>, std::greater<>> MinDataHeap;
typedef std::vector<MinDataHeap> MinDataHeapMatrix;
namespace MODINT {
constexpr valueType MOD = 998244353;
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>
void Inc(T1 &a, T2 b) {
a += b;
if (a >= MOD)
a -= MOD;
}
template<typename T1, typename T2>
void Dec(T1 &a, T2 b) {
a -= b;
if (a < 0)
a += MOD;
}
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
using namespace MODINT;
template<typename T, class Compare_ = std::less<>>
class SparseTable {
private:
valueType N, K;
std::vector<std::vector<T>> data;
public:
SparseTable() = default;
explicit SparseTable(valueType n, std::vector<T> const &source) : N(n), K(std::__lg(N)), data(K + 1, std::vector<T>(N + 1)) {
for (valueType i = 1; i <= N; ++i)
data[0][i] = source[i];
for (valueType k = 1; k <= K; ++k) {
for (valueType i = 1; i + (1 << k) - 1 <= N; ++i)
data[k][i] = std::min(data[k - 1][i], data[k - 1][i + (1 << (k - 1))], Compare_());
}
}
T Query(valueType l, valueType r) const {
valueType const len = r - l + 1;
valueType const k = std::__lg(len);
return std::min(data[k][l], data[k][r - (1 << k) + 1], Compare_());
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("ttds.in", "r", stdin);
freopen("ttds.out", "w", stdout);
#endif
valueType N, K, B;
std::cin >> N >> K >> B;
PairVector A(N + 1);
for (valueType i = 1; i <= N; ++i) {
std::cin >> A[i].first;
A[i].second = i;
}
valueType const InvB = pow(B, MOD - 2);
SparseTable<ValuePair, std::less<>> const ST(N, A);
MinDataHeapMatrix Queue(N + 1);
auto Flush = [&](valueType n) -> void {
MinDataHeap().swap(Queue[n]);
if (n < N)
Queue[n].emplace(ST.Query(n + 1, N), n + 1, N); // weight, l, r
};
auto Get = [&](valueType n) -> ValueVector {
if (Queue[n].empty())
return ValueVector();
ValueVector result;
valueType const V = std::get<0>(Queue[n].top()).first;
while (!Queue[n].empty()) {
auto const [w, l, r] = Queue[n].top();
if (w.first != V)
break;
Queue[n].pop();
valueType const x = w.second;
result.push_back(x);
if (l < x)
Queue[n].emplace(ST.Query(l, x - 1), l, x - 1);
if (x < r)
Queue[n].emplace(ST.Query(x + 1, r), x + 1, r);
}
return result;
};
ValueVector Ans;
valueType now = 0;
ValueVector Prefix;
auto PushBack = [&](valueType x) -> valueType {
Prefix.push_back(x);
Mul(now, B);
Inc(now, x);
return now;
};
auto PopBack = [&]() -> valueType {
valueType const x = Prefix.back();
Prefix.pop_back();
Dec(now, x);
Mul(now, InvB);
return now;
};
auto Solve = [&](auto self, PairVector const &S) -> void {
if (Ans.size() >= K)
return;
valueType count = 0;
for (auto const &[pos, w] : S)
if (pos >= 1)
count += w;
Ans.insert(Ans.end(), count, now);
Flush(S.front().first);
while (Ans.size() < K) {
ValueVector const NextPos = Get(S.front().first);
if (NextPos.empty())
return;
PairVector Next;
for (auto const &x : NextPos) {
valueType sum = 0;
for (auto const &[y, w] : S) {
if (y < x)
sum += w;
else
break;
}
Next.emplace_back(x, sum);
}
valueType const v = A[NextPos.front()].first;
PushBack(v);
self(self, Next);
PopBack();
}
};
PairVector Start({ValuePair(0, 1)});
Solve(Solve, Start);
Ans.resize(K);
for (auto const &x : Ans)
std::cout << x << '\n';
std::cout << std::flush;
return 0;
}