Solution Set【2023.12.28】
[NOI2015] 品酒大会
若建出后缀树,我们可以发现,产生贡献的是每个点对。考虑在其最近公共祖先处统计答案。
因此对于每个点,我们需要统计其子树中每个权值的最大值和最小值,以及子树大小即可解出答案。
使用后缀自动机建出后缀树,然后统计即可。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::string string;
typedef std::vector<bool> bitset;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
constexpr valueType MIN = std::numeric_limits<valueType>::min(), MAX = std::numeric_limits<valueType>::max();
class SuffixAutomaton {
public:
constexpr static valueType CharSetSize = 26;
typedef std::array<valueType, CharSetSize> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
// main data
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
// maybe useful
ValueVector TopoOrder;
// data for function extend
valueType Last;
// customed data for selected problems (P4248)
ValueVector EndPosCount;
ValueMatrix ParentTree;
// PairVector Min /*first min, second min*/, Max /*first max, second max*/;
ValueVector Min, Max;
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), Last(0), Min(2 * N + 1, MAX), Max(2 * N + 1, MIN) {
Link[0] = -1;
for (auto &p : Transfer)
p.fill(0);
}
void extend(char ch, valueType w) {
ch = ch - 'a';
valueType New = ++UsedPoolSize;
Length[New] = Length[Last] + 1;
Min[New] = Max[New] = w;
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];
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 bucket(N + 1, 0);
for (valueType i = 0; i <= UsedPoolSize; ++i)
++bucket[Length[i]];
for (valueType i = 1; i <= N; ++i)
bucket[i] += bucket[i - 1];
for (valueType i = 0; i <= UsedPoolSize; ++i)
TopoOrder[--bucket[Length[i]]] = i;
std::reverse(TopoOrder.begin(), TopoOrder.end());
}
public:
// customed functions for selected problems (P2178)
std::pair<ValueVector, ValueVector> Ans() {
ValueVector MaxAns(N + 1, MIN), SumAns(N + 1, 0);
EndPosCount.resize(UsedPoolSize + 1, 0);
ParentTree.resize(UsedPoolSize + 1);
for (valueType i = 1; i <= UsedPoolSize; ++i)
if (!Cloned[i])
EndPosCount[i] = 1;
for (auto const &u : TopoOrder) {
if (Link[u] != -1)
ParentTree[Link[u]].push_back(u);
}
for (auto const &u : TopoOrder) {
if (ParentTree[u].empty())
continue;
for (auto const &v : ParentTree[u]) {
if (Max[v] != MIN && Max[u] != MIN)
MaxAns[Length[u]] = std::max(MaxAns[Length[u]], Max[v] * Max[u]);
if (Min[v] != MAX && Min[u] != MAX)
MaxAns[Length[u]] = std::max(MaxAns[Length[u]], Min[v] * Min[u]);
Max[u] = std::max(Max[u], Max[v]);
Min[u] = std::min(Min[u], Min[v]);
SumAns[Length[u]] += EndPosCount[u] * EndPosCount[v];
EndPosCount[u] += EndPosCount[v];
}
}
for (valueType i = N - 1; i >= 0; --i) {
SumAns[i] += SumAns[i + 1];
MaxAns[i] = std::max(MaxAns[i], MaxAns[i + 1]);
}
for (auto &p : MaxAns)
if (p == MIN)
p = 0;
return std::make_pair(MaxAns, SumAns);
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N;
std::cin >> N;
std::string S;
std::cin >> S;
ValueVector W(N);
for (auto &p : W)
std::cin >> p;
std::reverse(S.begin(), S.end());
std::reverse(W.begin(), W.end());
SuffixAutomaton SAM(N);
for (valueType i = 0; i < N; ++i)
SAM.extend(S[i], W[i]);
SAM.GetTopoOrder();
auto [MaxAns, SumAns] = SAM.Ans();
for (valueType i = 0; i < N; ++i)
std::cout << SumAns[i] << ' ' << MaxAns[i] << '\n';
std::cout << std::flush;
return 0;
}
[AHOI2013] 差异
将题目中的算式放到后缀树中考虑,发现其实就是求后缀树中每个点对的带权距离。
考虑计算每条边的贡献,即每条边的权值乘上其两端点的子树大小之积。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::string string;
typedef std::vector<bool> bitset;
class SuffixAutomaton {
public:
constexpr static valueType CharSetSize = 26;
typedef std::array<valueType, CharSetSize> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
// main data
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
// maybe useful
ValueVector TopoOrder;
// data for function extend
valueType Last;
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), Last(0) {
Link[0] = -1;
for (auto &p : Transfer)
p.fill(0);
}
explicit SuffixAutomaton(string const &s) : SuffixAutomaton(s.size()) {
for (auto const &ch : s)
extend(ch);
}
void extend(char ch) {
ch = ch - 'a';
valueType New = ++UsedPoolSize;
Length[New] = Length[Last] + 1;
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];
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 bucket(N + 1, 0);
for (valueType i = 0; i <= UsedPoolSize; ++i)
++bucket[Length[i]];
for (valueType i = 1; i <= N; ++i)
bucket[i] += bucket[i - 1];
for (valueType i = 0; i <= UsedPoolSize; ++i)
TopoOrder[--bucket[Length[i]]] = i;
std::reverse(TopoOrder.begin(), TopoOrder.end());
}
public:
// customed functions for selected problems (P4248)
valueType Ans() const {
valueType ans = 0;
ValueVector EndPosCount(UsedPoolSize + 1, 0);
for (valueType i = 1; i <= UsedPoolSize; ++i)
if (!Cloned[i])
EndPosCount[i] = 1;
for (auto const &u : TopoOrder) {
if (Link[u] != -1)
EndPosCount[Link[u]] += EndPosCount[u];
}
for (auto const &u : TopoOrder) {
if (Link[u] != -1)
ans += EndPosCount[u] * (EndPosCount[0] - EndPosCount[u]) * (Length[u] - Length[Link[u]]);
}
return ans;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
std::string S;
std::cin >> S;
std::reverse(S.begin(), S.end());
SuffixAutomaton SAM(S);
SAM.GetTopoOrder();
std::cout << SAM.Ans() << std::endl;
return 0;
}
【模板】广义后缀自动机(广义 SAM)
将所有字符串插入字典树,然后按字典树的拓扑序依次将节点插入后缀自动机中即可。
Code
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::string string;
typedef std::vector<bool> bitset;
class GeneralSuffixAutomaton {
public:
constexpr static valueType CharSetSize = 26;
typedef std::array<valueType, CharSetSize> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
private:
valueType insertOnTrie(valueType current, valueType ch) {
if (Transfer[current][ch])
return Transfer[current][ch];
else
return Transfer[current][ch] = ++UsedPoolSize;
}
valueType expand(valueType Last, valueType ch) {
valueType New = Transfer[Last][ch];
if (Length[New] != 0)
return New;
Length[New] = Length[Last] + 1;
valueType P = Link[Last];
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;
Length[Clone] = Length[P] + 1;
Link[Clone] = Link[Q];
for (valueType i = 0; i < CharSetSize; ++i)
Transfer[Clone][i] = Length[Transfer[Q][i]] != 0 ? Transfer[Q][i] : 0;
Cloned[Clone] = true;
while (P != -1 && Transfer[P][ch] == Q) {
Transfer[P][ch] = Clone;
P = Link[P];
}
Link[Q] = Link[New] = Clone;
}
}
return New;
}
public:
GeneralSuffixAutomaton() = default;
explicit GeneralSuffixAutomaton(valueType n) : N(n), UsedPoolSize(0), Link(2 * N + 1), Length(2 * N + 1), Transfer(2 * N + 1), Cloned(2 * N + 1, false) {
Link[0] = -1;
for (auto &p : Transfer)
p.fill(0);
}
void insert(string const &s) {
valueType current = 0;
for (auto ch : s)
current = insertOnTrie(current, ch - 'a');
}
void insert(ValueVector const &s) {
valueType current = 0;
for (auto ch : s)
current = insertOnTrie(current, ch);
}
void build() {
std::queue<std::pair<valueType, valueType>> Q;
for (valueType i = 0; i < CharSetSize; ++i)
if (Transfer[0][i])
Q.emplace(0, i);
while (!Q.empty()) {
auto [Last, ch] = Q.front();
Q.pop();
valueType New = expand(Last, ch);
for (valueType i = 0; i < CharSetSize; ++i)
if (Transfer[New][i])
Q.emplace(New, i);
}
}
long long Ans() const {
long long ans = 0;
for (valueType i = 1; i <= UsedPoolSize; ++i)
ans += Length[i] - Length[Link[i]];
return ans;
}
valueType size() const {
return UsedPoolSize + 1;
}
};
constexpr valueType MAXN = 1e6 + 5;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N;
std::cin >> N;
GeneralSuffixAutomaton SA(MAXN);
for (valueType i = 0; i < N; ++i) {
string s;
std::cin >> s;
SA.insert(s);
}
SA.build();
std::cout << SA.Ans() << std::endl << SA.size() << std::endl;
return 0;
}
诸神眷顾的幻想乡
发现树链上的字符串一定为从某个叶子出发得到的所有字符串中的子串。
因此我们可以将从所有叶子节点出发得到的所有字符串插入广义后缀自动机中,然后求出本质不同子串个数即可。
Code
#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 GeneralSuffixAutomaton {
public:
constexpr static valueType CharSetSize = 10;
typedef std::array<valueType, CharSetSize> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
private:
valueType insertOnTrie(valueType current, valueType ch) {
if (Transfer[current][ch])
return Transfer[current][ch];
else
return Transfer[current][ch] = ++UsedPoolSize;
}
valueType expand(valueType Last, valueType ch) {
valueType New = Transfer[Last][ch];
if (Length[New] != 0)
return New;
Length[New] = Length[Last] + 1;
valueType P = Link[Last];
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;
Length[Clone] = Length[P] + 1;
Link[Clone] = Link[Q];
for (valueType i = 0; i < CharSetSize; ++i)
Transfer[Clone][i] = Length[Transfer[Q][i]] != 0 ? Transfer[Q][i] : 0;
Cloned[Clone] = true;
while (P != -1 && Transfer[P][ch] == Q) {
Transfer[P][ch] = Clone;
P = Link[P];
}
Link[Q] = Link[New] = Clone;
}
}
return New;
}
public:
GeneralSuffixAutomaton() = default;
explicit GeneralSuffixAutomaton(valueType n) : N(n), UsedPoolSize(0), Link(2 * N + 1), Length(2 * N + 1), Transfer(2 * N + 1), Cloned(2 * N + 1, false) {
Link[0] = -1;
for (auto &p : Transfer)
p.fill(0);
}
void insert(string const &s) {
valueType current = 0;
for (auto ch : s)
current = insertOnTrie(current, ch - 'a');
}
void insert(ValueVector const &s) {
valueType current = 0;
for (auto ch : s)
current = insertOnTrie(current, ch);
}
void build() {
std::queue<std::pair<valueType, valueType>> Q;
for (valueType i = 0; i < CharSetSize; ++i)
if (Transfer[0][i])
Q.emplace(0, i);
while (!Q.empty()) {
auto [Last, ch] = Q.front();
Q.pop();
valueType New = expand(Last, ch);
for (valueType i = 0; i < CharSetSize; ++i)
if (Transfer[New][i])
Q.emplace(New, i);
}
}
long long Ans() const {
long long ans = 0;
for (valueType i = 1; i <= UsedPoolSize; ++i)
ans += Length[i] - Length[Link[i]];
return ans;
}
valueType size() const {
return UsedPoolSize + 1;
}
};
constexpr valueType MAXN = 2e6 + 5;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N, M;
std::cin >> N >> M;
GeneralSuffixAutomaton SA(MAXN);
ValueVector C(N + 1), D(N + 1, 0);
ValueMatrix G(N + 1);
for (valueType i = 1; i <= N; ++i)
std::cin >> C[i];
for (valueType i = 1; i < N; ++i) {
valueType u, v;
std::cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
++D[u];
++D[v];
}
std::function<void(valueType, valueType, ValueVector &)> dfs = [&](valueType u, valueType from, ValueVector &path) {
path.push_back(C[u]);
if (D[u] == 1 && from != -1)
SA.insert(path);
for (auto v : G[u]) {
if (v == from)
continue;
dfs(v, u, path);
}
path.pop_back();
};
ValueVector path;
for (valueType i = 1; i <= N; ++i)
if (D[i] == 1)
dfs(i, -1, path);
SA.build();
std::cout << SA.Ans() << std::endl;
return 0;
}
[CTSC2012] 熟悉的文章
发现答案具有单调性,因此可以二分答案,下面考虑如何判定答案。
判定的主要任务是求出一种划分方式,使得被匹配的长度和最大。不妨设 \(f_i\) 代表文本串前缀 \(s_{1 \dots i}\) 通过某种划分方式被匹配的最大长度。
那么通过枚举上一次划分的位置,我们可以得到状态转移方程:
\[f_i = \max\left\{f_{i - 1}, \max_{j < i - L} \{f_j + i - j\}\right\}
\]
上式中 \(L\) 代表判定的答案,同时我们发现上述式子中对 \(j\) 的限制是不完全的,因为我们还需要限制 \(s_{j + 1, i}\) 在模式串中出现。设 \(l_i\) 代表文本串前缀 \(s_{1 \dots i}\) 最长的在模式串中出现的后缀的长度,该值可以通过对模式串建出广义后缀自动机后对文本串进行匹配得出。那么合法的 \(j\) 应该满足 \(j \in \left[i - l_i, i - L\right]\)
由于该区间的左右端点均是单调的,因此可以使用单调队列优化转移。
复杂度为 \(O(n \log n)\),其中 \(n\) 为文本串长度。
Code
#include <bits/stdc++.h>
typedef int valueType;
typedef std::vector<valueType> ValueVector;
typedef std::string string;
typedef std::vector<bool> bitset;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::deque<ValuePair> PairQueue;
template<valueType CharSetSize, valueType BaseChar>
class GeneralSuffixAutomaton {
public:
typedef std::array<valueType, CharSetSize> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
private:
valueType insertOnTrie(valueType current, valueType ch) {
if (Transfer[current][ch])
return Transfer[current][ch];
else
return Transfer[current][ch] = ++UsedPoolSize;
}
valueType expand(valueType Last, valueType ch) {
valueType New = Transfer[Last][ch];
if (Length[New] != 0)
return New;
Length[New] = Length[Last] + 1;
valueType P = Link[Last];
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;
Length[Clone] = Length[P] + 1;
Link[Clone] = Link[Q];
for (valueType i = 0; i < CharSetSize; ++i)
Transfer[Clone][i] = Length[Transfer[Q][i]] != 0 ? Transfer[Q][i] : 0;
Cloned[Clone] = true;
while (P != -1 && Transfer[P][ch] == Q) {
Transfer[P][ch] = Clone;
P = Link[P];
}
Link[Q] = Link[New] = Clone;
}
}
return New;
}
public:
GeneralSuffixAutomaton() = default;
explicit GeneralSuffixAutomaton(valueType n) : N(n), UsedPoolSize(0), Link(2 * N + 1), Length(2 * N + 1), Transfer(2 * N + 1), Cloned(2 * N + 1, false) {
Link[0] = -1;
for (auto &p : Transfer)
p.fill(0);
}
void insert(string const &s) {
valueType current = 0;
for (auto ch : s)
current = insertOnTrie(current, ch - BaseChar);
}
void insert(ValueVector const &s) {
valueType current = 0;
for (auto ch : s)
current = insertOnTrie(current, ch);
}
void build() {
std::queue<std::pair<valueType, valueType>> Q;
for (valueType i = 0; i < CharSetSize; ++i)
if (Transfer[0][i])
Q.emplace(0, i);
while (!Q.empty()) {
auto [Last, ch] = Q.front();
Q.pop();
valueType New = expand(Last, ch);
for (valueType i = 0; i < CharSetSize; ++i)
if (Transfer[New][i])
Q.emplace(New, i);
}
}
public:
// extra functions
long long GetSubStringCount() const {
long long ans = 0;
for (valueType i = 1; i <= UsedPoolSize; ++i)
ans += Length[i] - Length[Link[i]];
return ans;
}
valueType size() const {
return UsedPoolSize + 1;
}
ValueVector Match(ValueVector const &S, valueType start) const {
valueType current = 0, len = 0;
ValueVector ans(S.size(), 0);
while (start < S.size()) {
valueType const ch = S[start];
if (Transfer[current][ch]) {
current = Transfer[current][ch];
++len;
} else {
while (current != -1 && !Transfer[current][ch])
current = Link[current];
if (current == -1) {
current = len = 0;
} else {
len = Length[current] + 1;
current = Transfer[current][ch];
}
}
ans[start] = len;
++start;
}
return ans;
}
ValueVector Match(string const &S, valueType start) const {
valueType current = 0, len = 0;
ValueVector ans(S.size(), 0);
while (start < S.size()) {
valueType const ch = S[start] - BaseChar;
if (Transfer[current][ch]) {
current = Transfer[current][ch];
++len;
} else {
while (current != -1 && !Transfer[current][ch])
current = Link[current];
if (current == -1) {
current = len = 0;
} else {
len = Length[current] + 1;
current = Transfer[current][ch];
}
}
ans[start] = len;
++start;
}
return ans;
}
};
constexpr valueType MAXN = 1e6 + 1e5 + 5;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType T, M;
std::cin >> T >> M;
GeneralSuffixAutomaton<2, '0'> SA(MAXN);
for (valueType i = 0; i < M; ++i) {
string s;
std::cin >> s;
SA.insert(s);
}
SA.build();
for (valueType testcase = 0; testcase < T; ++testcase) {
string S;
std::cin >> S;
valueType const N = S.size();
auto length = SA.Match(S, 0);
length.insert(length.begin(), 0);
auto check = [&](valueType Limit) -> bool {
ValueVector F(N + 1, 0);
F[0] = 0;
PairQueue Q;
for (valueType i = 1; i <= N; ++i) {
F[i] = F[i - 1];
if (i >= Limit) {
ValuePair next(F[i - Limit] - (i - Limit), i - Limit);
while (!Q.empty() && Q.back().first <= next.first)
Q.pop_back();
Q.push_back(next);
}
while (!Q.empty() && Q.front().second < i - length[i])
Q.pop_front();
if (!Q.empty())
F[i] = std::max(F[i], Q.front().first + i);
}
return F[N] * 10 >= N * 9;
};
valueType L = 0, R = N, ans = 0;
while (L <= R) {
valueType const mid = (L + R) / 2;
if (check(mid)) {
ans = mid;
L = mid + 1;
} else {
R = mid - 1;
}
}
std::cout << ans << '\n';
}
std::cout << std::flush;
return 0;
}