Solution Set【2023.12.29】
A. 九王唱
对于最优策略的理解可以认为是每个人都在尽可能的避免拿到评估低的物品,所以每个人都会选择删除自己评估最低的物品,但是若某个物品被两个人评估最低,那么先操作的人就可以不操作这个物品,因为后面的人会帮他删除这个物品,所以逆序遍历,并使得每个人都删除自己评估最低的物品,这样就可以得到最优策略。
直接模拟的话复杂度是 \(\mathcal{O}(n^3)\) 的,考虑如何优化。发现当起点从 \(x - 1\) 变为 \(x\) 后,每个人选择删除的物品的评估值是单调不减的,因为前面多了一个人来帮忙删除。而唯一会改变的是 \(x\) 的选择,因此维护出每个人选择的物品的评估值,就可以在 \(\mathcal{O}(n^2)\) 的时间内求出答案。
Coed
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::vector<bool> bitset;
typedef std::pair<valueType, valueType> ValuePair;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
#ifndef LOCAL_STDIO
freopen("sing.in", "r", stdin);
freopen("sing.out", "w", stdout);
#endif
valueType N, seed;
std::cin >> N >> seed;
ValueMatrix A(N + 1, ValueVector(N + 2));
if (seed == 0) {
for (valueType i = 1; i <= N; i++)
for (valueType j = 1; j <= N + 1; j++)
std::cin >> A[i][j];
} else {
std::mt19937 engine(seed);
for (valueType i = 1; i <= N; i++) {
for (valueType j = 1; j <= N + 1; j++) {
A[i][j] = j;
std::swap(A[i][j], A[i][engine() % j + 1]);
}
}
}
ValueMatrix B(N + 1, ValueVector(N + 2));
for (valueType i = 1; i <= N; i++) {
for (valueType j = 1; j <= N + 1; j++) {
B[i][A[i][j]] = j;
}
}
ValueVector choose(N + 1, 0);
for (valueType i = 1; i <= N; ++i)
choose[i] = 1;
for (valueType start = 1; start <= N; start++) {
bitset exist(N + 2, true);
valueType ans = 0;
choose[(start - 2 + N) % N + 1] = 1;
for (valueType i = 1; i <= N; ++i) {
valueType const now = (start - i - 1 + N) % N + 1;
while (!exist[B[now][choose[now]]])
++choose[now];
exist[B[now][choose[now]]] = false;
}
for (valueType i = 1; i <= N + 1; ++i) {
if (exist[i]) {
ans = i;
break;
}
}
std::cout << ans << ' ';
}
std::cout << std::endl;
return 0;
}
B. 数数
对于 \(n\) 个在 \(\left[0, 1\right]\) 中独立随机分布的变量,其最小值期望为 \(\frac{1}{n + 1}\)。因此我们可以将每个数随机分配一个随机值,然后求出集合的最小值,这样就可以估计答案。
实际实现过程中,我们可以将随机值的区间设置为 \(\left[0, 2^{32}\right)\),以避免存储浮点数带来的麻烦。同时可以维护多个映射并按平均值计算答案以减少误差。
题目结论的推导需要使用到积分,挂一个链接。
Coed
#include "count.h"
#include <bits/stdc++.h>
typedef unsigned long long u64;
typedef unsigned int valueType;
typedef long double realType;
typedef std::mt19937 Engine;
void init(void *mem) {
constexpr valueType MAX = std::numeric_limits<valueType>::max();
valueType *ptr = (valueType *) mem;
for (valueType i = 0; i < 8; ++i)
ptr[i] = MAX;
}
void insert(u64 data, void *mem) {
constexpr valueType MAX = std::numeric_limits<valueType>::max();
valueType *ptr = (valueType *) mem;
Engine engine(data);
for (valueType i = 0; i < 8; ++i)
ptr[i] = std::min(ptr[i], engine());
}
u64 count(void *mem) {
constexpr valueType MAX = std::numeric_limits<valueType>::max();
valueType *ptr = (valueType *) mem;
realType sum = 0;
for (valueType i = 0; i < 8; ++i)
sum += ptr[i];
return ((u64) ((realType) MAX / (sum / 8) - 1));
return 0;
}
[NOI2018] 你的名字
首先考虑不存在区间询问的时候怎么做,发现没有出现过的子串不好统计,正难则反,转为统计有多少子串出现过,这样就可以用后缀自动机来做了。
具体的,对 \(S\) 和 \(T\) 分别建立后缀自动机,通过将 \(T\) 在 \(S\) 的后缀自动机上匹配可以得出 \(T\) 的每个前缀在 \(S\) 中出现的最长后缀长度。对于要求只统计本质不同字符串的限制可以将统计答案的部分放到 \(T\) 的后缀自动机上去做。发现若可以得知每个节点在 \(S\) 中出现的最长后缀长度,那么就可以得到每个节点对答案的贡献,因此可以在 \(T\) 的后缀自动机上维护每个节点所代表的 \(\tt{endpos}\) 集合的一个特征值,不妨维护 \(\tt{endpos}\) 的最小值,这样就可以在 \(T\) 的后缀自动机上维护答案了。
对于区间询问,发现其实质上是限制了 \(T\) 在 \(S\) 的后缀自动机上匹配时可以访问的节点集合,故我们需要维护处 \(S\) 的后缀自动机上的 \(\tt{endpos}\) 集合。使用线段树合并维护出每个节点的 \(\tt{endpos}\) 集合,这样就可以进行匹配了。
复杂度为 \(\mathcal{O}(\left\lvert S \right\rvert \log \left\lvert S \right\rvert + \sum \left\lvert T \right\rvert \log \left\lvert S \right\rvert)\)。
Coed
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::string string;
typedef std::vector<bool> bitset;
class MergeableSegmentTree {
private:
valueType N, M, UsedPoolSize;
ValueVector LeftSon, RightSon, data;
ValueVector Root;
void merge(valueType id) {
data[id] = data[LeftSon[id]] + data[RightSon[id]];
}
public:
MergeableSegmentTree() = default;
MergeableSegmentTree(valueType n, valueType m, valueType k = 4) : N(n), M(m), UsedPoolSize(1) {
valueType const size = std::ceil(m * std::log2(n) * k) + 100;
LeftSon.resize(size, 0);
RightSon.resize(size, 0);
data.resize(size, 0);
Root.resize(m + 1, 0);
}
void insert(valueType root, valueType pos, valueType value = 1) {
insert(Root[root], 1, N, pos, value);
}
valueType query(valueType root, valueType l, valueType r) const {
if (l > r)
return 0;
return query(Root[root], 1, N, l, r);
}
void merge(valueType root, valueType a, valueType b) {
Root[root] = merge(Root[a], Root[b], 1, N);
}
private:
void insert(valueType ¤t, valueType l, valueType r, valueType pos, valueType value) {
if (current == 0)
current = ++UsedPoolSize;
if (l == r) {
data[current] += value;
return;
}
valueType const mid = (l + r) >> 1;
if (pos <= mid)
insert(LeftSon[current], l, mid, pos, value);
else
insert(RightSon[current], mid + 1, r, pos, value);
merge(current);
}
valueType query(valueType current, valueType nodeL, valueType nodeR, valueType queryL, valueType queryR) const {
if (queryL <= nodeL && nodeR <= queryR)
return data[current];
valueType const mid = (nodeL + nodeR) >> 1;
if (queryR <= mid)
return query(LeftSon[current], nodeL, mid, queryL, queryR);
else if (queryL > mid)
return query(RightSon[current], mid + 1, nodeR, queryL, queryR);
else
return query(LeftSon[current], nodeL, mid, queryL, queryR) + query(RightSon[current], mid + 1, nodeR, queryL, queryR);
}
valueType merge(valueType a, valueType b, valueType l, valueType r) {
if (a == 0)
return b;
if (b == 0)
return a;
valueType const New = ++UsedPoolSize;
data[New] = data[a] + data[b];
if (l == r)
return New;
valueType const mid = (l + r) >> 1;
LeftSon[New] = merge(LeftSon[a], LeftSon[b], l, mid);
RightSon[New] = merge(RightSon[a], RightSon[b], mid + 1, r);
merge(New);
return New;
}
};
template<valueType CharSetSize, valueType BaseChar>
class SuffixAutomaton {
public:
typedef std::array<valueType, CharSetSize> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
// main data
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
ValueVector FirstPos;
// data for function extend
valueType Last;
// maybe useful
ValueVector TopoOrder;
ValueVector EndPosCount;
public:
SuffixAutomaton() = default;
explicit SuffixAutomaton(valueType n) : N(n), UsedPoolSize(0), Link(2 * N + 1), Length(2 * N + 1), Transfer(2 * N + 1), Cloned(2 * N + 1, false), FirstPos(2 * N + 1, 0), Last(0) {
Link[0] = -1;
for (auto &p : Transfer)
p.fill(0);
}
explicit SuffixAutomaton(string const &S) : SuffixAutomaton(S.size()) {
for (size_t i = 0; i < S.size(); ++i)
extend(S[i] - BaseChar, i + 1);
}
explicit SuffixAutomaton(ValueVector const &S) : SuffixAutomaton(S.size()) {
for (size_t i = 0; i < S.size(); ++i)
extend(S[i], i + 1);
}
void extend(valueType ch, valueType pos) {
valueType New = ++UsedPoolSize;
Length[New] = Length[Last] + 1;
FirstPos[New] = pos;
valueType P = Last;
Last = New;
while (P != -1 && !Transfer[P][ch]) {
Transfer[P][ch] = New;
P = Link[P];
}
if (P == -1) {
Link[New] = 0;
} else {
valueType Q = Transfer[P][ch];
if (Length[P] + 1 == Length[Q]) {
Link[New] = Q;
} else {
valueType Clone = ++UsedPoolSize;
Cloned[Clone] = true;
Length[Clone] = Length[P] + 1;
Link[Clone] = Link[Q];
Transfer[Clone] = Transfer[Q];
FirstPos[Clone] = FirstPos[Q];
while (P != -1 && Transfer[P][ch] == Q) {
Transfer[P][ch] = Clone;
P = Link[P];
}
Link[Q] = Link[New] = Clone;
}
}
}
public:
// extra functions
void GetTopoOrder() {
TopoOrder.resize(UsedPoolSize + 1);
ValueVector Count(N + 1, 0);
for (valueType i = 0; i <= UsedPoolSize; ++i)
++Count[Length[i]];
for (valueType i = 1; i <= N; ++i)
Count[i] += Count[i - 1];
for (valueType i = 0; i <= UsedPoolSize; ++i)
TopoOrder[--Count[Length[i]]] = i;
std::reverse(TopoOrder.begin(), TopoOrder.end());
}
void GetEndPosCount() {
EndPosCount.resize(UsedPoolSize + 1);
std::fill(EndPosCount.begin(), EndPosCount.end(), 0);
for (valueType i = 0; i <= UsedPoolSize; ++i)
if (!Cloned[i])
EndPosCount[i] = 1;
for (auto const &u : TopoOrder)
if (Link[u] != -1)
EndPosCount[Link[u]] += EndPosCount[u];
EndPosCount[0] = 0;
}
public:
// customed functions for selected problems (P4770)
ValueVector Match(ValueVector const &S, valueType l, valueType r, MergeableSegmentTree const &tree) {
ValueVector result(S.size() + 1);
valueType current = 0, len = 0;
for (valueType i = 0; i < S.size(); ++i) {
while (true) {
if (Transfer[current][S[i]] != 0 && tree.query(Transfer[current][S[i]], l + len, r) > 0) {
current = Transfer[current][S[i]];
++len;
break;
}
if (len == 0)
break;
--len;
if (len == Length[Link[current]])
current = Link[current];
}
result[i + 1] = len;
}
return result;
}
ValueVector Match(std::string const &S, valueType l, valueType r, MergeableSegmentTree const &tree) {
ValueVector result(S.size() + 1);
valueType current = 0, len = 0;
for (size_t i = 0; i < S.size(); ++i) {
valueType const ch = S[i] - BaseChar;
while (true) {
if (Transfer[current][ch] != 0 && tree.query(Transfer[current][ch], l + len, r) > 0) {
current = Transfer[current][ch];
++len;
break;
}
if (len == 0)
break;
--len;
if (len == Length[Link[current]])
current = Link[current];
}
result[i + 1] = len;
}
return result;
}
void BuildMergeableSegmentTree(MergeableSegmentTree &tree) {
for (valueType i = 0; i <= UsedPoolSize; ++i)
if (!Cloned[i])
tree.insert(i, FirstPos[i]);
for (auto const &u : TopoOrder)
if (Link[u] != -1)
tree.merge(Link[u], Link[u], u);
}
long long Ans(ValueVector const &Match) {
long long ans = 0;
for (valueType i = 1; i <= UsedPoolSize; ++i)
ans += std::max<long long>(0, Length[i] - std::max(Length[Link[i]], Match[FirstPos[i]]));
return ans;
}
valueType size() const {
return UsedPoolSize + 1;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
string S;
std::cin >> S;
valueType const N = S.size();
SuffixAutomaton<26, 'a'> SAM(S);
MergeableSegmentTree tree(N, SAM.size());
SAM.GetTopoOrder();
SAM.BuildMergeableSegmentTree(tree);
valueType Q;
std::cin >> Q;
for (valueType q = 0; q < Q; ++q) {
std::string T;
std::cin >> T;
valueType l, r;
std::cin >> l >> r;
std::cout << SuffixAutomaton<26, 'a'>(T).Ans(SAM.Match(T, l, r, tree)) << '\n';
}
std::cout << std::flush;
return 0;
}