Solution Set【2023.12.27】
CF1909D Split Plus K
观察到操作对于每一个数的影响是独立的,所以可以分别考虑每一个数。
若一个数 \(x\) 共被操作了 \(y\) 次,那么最终会得到的值为
进行一些变换,可以得到
那么我们的目标是使得所有数的 \(\dfrac{x - k}{y + 1}\) 均相等,不难想到这个值就是所有 \(x - k\) 的最大公约数。
可以发现分式的值取最大公约数时合法且 \(y\) 最小,若取最大公约数的因子,那么 \(y\) 会更大,所以我们只需要取最大公约数即可。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::vector<ValueMatrix> ValueCube;
typedef std::string string;
typedef std::vector<string> StringVector;
typedef std::vector<bool> bitset;
typedef std::vector<bitset> BitMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
typedef std::map<valueType, valueType> ValueSet;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType T;
std::cin >> T;
for (valueType testcase = 0; testcase < T; ++testcase) {
valueType N, K;
std::cin >> N >> K;
ValueVector set(N);
for (auto &iter : set)
std::cin >> iter;
std::sort(set.begin(), set.end());
if (set.front() == set.back()) {
std::cout << 0 << '\n';
continue;
}
if (set.front() < K && set.back() > K) {
std::cout << -1 << '\n';
continue;
}
bool existK = std::find(set.begin(), set.end(), K) != set.end();
if (existK) {
std::cout << -1 << '\n';
continue;
}
valueType gcd = set[0] - K;
for (valueType i = 1; i < N; ++i)
gcd = std::__gcd(gcd, set[i] - K);
valueType ans = 0;
for (auto const &x : set)
ans += (x - K) / gcd - 1;
std::cout << ans << '\n';
}
std::cout << std::flush;
return 0;
}
CF1909E Multiple Lamps
可以发现,若将按钮全部操作一遍,灯 \(i\) 被点亮当且仅当 \(i\) 的约数个数为奇数,即 \(i\) 为完全平方数。可以发现这样的数有 \(\lfloor \sqrt{n} \rfloor\) 个,因此当 \(n \ge 20\) 时将按钮全部操作一遍即可。
对于 \(n < 20\) 的情况,可以预处理出不考虑边的限制下所有合法方案,然后枚举每一个方案,若其满足边的限制那么就找到了一组合法方案。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::vector<ValueMatrix> ValueCube;
typedef std::string string;
typedef std::vector<string> StringVector;
typedef std::vector<bool> bitset;
typedef std::vector<bitset> BitMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
typedef std::map<valueType, valueType> ValueSet;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType T;
std::cin >> T;
ValueMatrix preWork(20);
for (valueType n = 1; n <= 19; ++n) {
ValueVector B(n + 1, 0);
for (valueType i = 1; i <= n; ++i)
for (valueType j = i; j <= n; j += i)
B[i] |= 1 << (j - 1);
for (valueType s = 1; s < (1 << n); ++s) {
valueType state = 0;
for (valueType i = 1; i <= n; ++i)
if (s & (1 << (i - 1)))
state ^= B[i];
valueType cnt = 0;
for (valueType i = 1; i <= n; ++i)
if (state & (1 << (i - 1)))
++cnt;
if (cnt <= n / 5)
preWork[n].emplace_back(s);
}
}
for (valueType testcase = 0; testcase < T; ++testcase) {
valueType N, M;
std::cin >> N >> M;
if (N > 19) {
for (valueType i = 0; i < M; ++i) {
valueType u, v;
std::cin >> u >> v;
}
std::cout << N << '\n';
for (valueType i = 1; i <= N; ++i)
std::cout << i << ' ';
std::cout << '\n';
continue;
}
PairVector limit(M);
for (auto &[u, v] : limit)
std::cin >> u >> v;
std::shuffle(limit.begin(), limit.end(), std::mt19937(std::random_device()()));
valueType ans = -1;
for (auto const &s : preWork[N]) {
bool ok = true;
for (auto const &[u, v] : limit)
if ((s & (1 << (u - 1))) && !(s & (1 << (v - 1)))) {
ok = false;
break;
}
if (ok) {
ans = s;
break;
}
}
if (ans == -1)
std::cout << -1 << '\n';
else {
std::cout << __builtin_popcount(ans) << '\n';
for (valueType i = 1; i <= N; ++i)
if (ans & (1 << (i - 1)))
std::cout << i << ' ';
std::cout << '\n';
}
}
std::cout << std::flush;
return 0;
}
CF1909F1 Small Permutation Problem (Easy Version) / CF1909F2 Small Permutation Problem (Hard Version)
若 \(a_i \neq -1\),设 \(j\) 是小于 \(i\) 中最大的满足 \(a_j \neq -1\) 的数。那么也就是在 \(i\) 之前排列中需要多出 \(d = a_i - a_j\) 个 \(\le i\) 的数。
考虑将数进行分类,在 \(\left[1, j\right]\) 中的数不能放在下标 \(\left[1, j\right]\) 中,否则会导致 \(a_j\) 改变,因此其只能放在 \(\left(j, i\right]\) 中。而在 \(\left(j, i\right]\) 中的数可以放在 \(\left[1, i\right]\) 中的任意位置。
为了避免重复,我们可以先放 \(\left[1, j\right]\) 中的数,再放 \(\left(j, i\right]\) 中的数,这样就不会重复了。故枚举 \(k\) 代表放了多少个 \(\left[1, j\right]\) 中的数,这部分的方案数为
接下来我们需要放 \(d - k\) 个在 \(\left(j, i\right]\) 中的数,这部分的方案数为
\(a_i\) 产生的贡献为
由于 \(\sum d = n\),因此总复杂度为 \(\mathcal{O}(n)\)。
Code
#include <bits/stdc++.h>
typedef long long valueType;
typedef std::vector<valueType> ValueVector;
typedef std::vector<ValueVector> ValueMatrix;
typedef std::vector<ValueMatrix> ValueCube;
typedef std::string string;
typedef std::vector<string> StringVector;
typedef std::vector<bool> bitset;
typedef std::vector<bitset> BitMatrix;
typedef std::pair<valueType, valueType> ValuePair;
typedef std::vector<ValuePair> PairVector;
typedef std::vector<PairVector> PairMatrix;
namespace MODINT_WITH_FIXED_MOD {
constexpr valueType MOD = 998244353;
template<typename T1, typename T2, typename T3 = valueType>
void Inc(T1 &a, T2 b, const T3 &mod = MOD) {
a = a + b;
if (a >= mod)
a -= mod;
}
template<typename T1, typename T2, typename T3 = valueType>
void Dec(T1 &a, T2 b, const T3 &mod = MOD) {
a = a - b;
if (a < 0)
a += mod;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 sum(T1 a, T2 b, const T3 &mod = MOD) {
return a + b >= mod ? a + b - mod : a + b;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 sub(T1 a, T2 b, const T3 &mod = MOD) {
return a - b < 0 ? a - b + mod : a - b;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 mul(T1 a, T2 b, const T3 &mod = MOD) {
return (long long) a * b % mod;
}
template<typename T1, typename T2, typename T3 = valueType>
void Mul(T1 &a, T2 b, const T3 &mod = MOD) {
a = (long long) a * b % mod;
}
template<typename T1, typename T2, typename T3 = valueType>
T1 pow(T1 a, T2 b, const T3 &mod = MOD) {
T1 result = 1;
while (b > 0) {
if (b & 1)
Mul(result, a, mod);
Mul(a, a, mod);
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);
valueType T;
std::cin >> T;
for (valueType testcase = 0; testcase < T; ++testcase) {
valueType N;
std::cin >> N;
ValueVector A(N + 1, 0);
for (valueType i = 1; i <= N; ++i)
std::cin >> A[i];
if (A[N] == -1)
A[N] = N;
if (A[N] != N) {
std::cout << 0 << '\n';
continue;
}
ValueVector Fact(N + 1, 0), InvFact(N + 1, 0);
Fact[0] = 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);
auto C = [&](valueType n, valueType m) -> valueType {
if (n < 0 || m < 0 || n < m)
return 0;
return mul(Fact[n], mul(InvFact[m], InvFact[n - m]));
};
PairVector B;
B.reserve(N + 1);
B.emplace_back(0, 0);
for (valueType i = 1; i <= N; ++i) {
if (A[i] == -1)
continue;
B.emplace_back(i, A[i]);
}
valueType ans = 1;
for (valueType p = 1; p < B.size(); ++p) {
auto const [i, Ai] = B[p];
auto const [j, Aj] = B[p - 1];
valueType sum = 0;
valueType const D = Ai - Aj;
if (D < 0) {
ans = 0;
break;
}
for (valueType k = 0; k <= D; ++k) {
valueType product = 1;
Mul(product, C(i - j, k));
Mul(product, C(j - Aj, k));
Mul(product, Fact[k]);
Mul(product, C(i - j, D - k));
Mul(product, C(i - Aj - k, D - k));
Mul(product, Fact[D - k]);
Inc(sum, product);
}
Mul(ans, sum);
}
std::cout << ans << '\n';
}
std::cout << std::flush;
return 0;
}
【模板】后缀自动机(SAM)
建出 \(\tt{SAM}\) 后,每个节点 \(\tt{endpos}\) 集合的大小即为其对应的子串出现次数,因此可以在建出 \(\tt{SAM}\) 后遍历后缀树,统计每个节点的 \(\tt{endpos}\) 集合的大小后更新答案即可。
可以发现,若某个节点是由新加入的字符而创建的,那么其 \(\tt{endpos}\) 集合的大小为 \(1\),否则其是由某个其他节点复制而来的,其 \(\tt{endpos}\) 集合的大小为 \(0\)。
同时,将节点按照 \(\tt{len}\) 从大到小排序,那么便可以获得 \(\tt{SAM}\) 的拓扑序,也是后缀树的拓扑序。
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;
template<typename T>
class SuffixAutomaton {
public:
typedef std::map<T, valueType> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
// main data
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
// maybe useful
ValueVector EndPosCount;
// 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;
}
void extend(T ch) {
valueType New = ++UsedPoolSize;
Length[New] = Length[Last] + 1;
valueType P = Last;
Last = New;
while (P != -1 && !Transfer[P].count(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 calcEndPosCount() {
EndPosCount.resize(2 * N + 1);
ValueMatrix ParentTree(2 * N + 1);
for (valueType i = 1; i <= UsedPoolSize; ++i) {
ParentTree[Link[i]].push_back(i);
if (!Cloned[i])
EndPosCount[i] = 1;
}
std::function<void(valueType)> dfs = [&](valueType u) {
for (auto &v : ParentTree[u]) {
dfs(v);
EndPosCount[u] += EndPosCount[v];
}
};
dfs(0);
EndPosCount[0] = 0;
}
public:
// customed functions for selected problems (P3804)
valueType Ans() const {
valueType ans = 0;
for (valueType i = 1; i <= UsedPoolSize; ++i)
if (EndPosCount[i] > 1)
ans = std::max(ans, Length[i] * EndPosCount[i]);
return ans;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
string S;
std::cin >> S;
SuffixAutomaton<char> SAM(S.size());
for (auto const &ch : S)
SAM.extend(ch);
SAM.calcEndPosCount();
std::cout << SAM.Ans() << std::endl;
return 0;
}
[SDOI2016] 生成魔咒
每个新增节点 \(u\) 对不同子串数目的贡献为 \(\operatorname{Len}(u) - \operatorname{Len}(\operatorname{Link}(u))\)。
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;
template<typename T>
class SuffixAutomaton {
public:
typedef std::map<T, valueType> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
// main data
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
// maybe useful
ValueVector EndPosCount;
// 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;
}
valueType extend(T ch) {
valueType New = ++UsedPoolSize;
Length[New] = Length[Last] + 1;
valueType P = Last;
Last = New;
while (P != -1 && !Transfer[P].count(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;
}
}
return Length[New] - Length[Link[New]];
}
public:
//extra functions
void calcEndPosCount() {
EndPosCount.resize(2 * N + 1);
ValueMatrix ParentTree(2 * N + 1);
for (valueType i = 1; i <= UsedPoolSize; ++i) {
ParentTree[Link[i]].push_back(i);
if (!Cloned[i])
EndPosCount[i] = 1;
}
std::function<void(valueType)> dfs = [&](valueType u) {
for (auto &v : ParentTree[u]) {
dfs(v);
EndPosCount[u] += EndPosCount[v];
}
};
dfs(0);
EndPosCount[0] = 0;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N;
std::cin >> N;
SuffixAutomaton<valueType> SAM(N);
valueType ans = 0;
for (valueType i = 0; i < N; ++i) {
valueType ch;
std::cin >> ch;
ans += SAM.extend(ch);
std::cout << ans << '\n';
}
std::cout << std::flush;
return 0;
}
[TJOI2015] 弦论
若本质相同的子串重复计算,那么每个节点的贡献为其 \(\tt{endpos}\) 集合的大小,否则为 \(1\)。
遍历 \(\tt{SAM}\) 后问题变为了求其第 \(k\) 小的路径,贪心的遍历即可。
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;
template<typename T>
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 EndPosCount;
ValueVector SubStrCount;
// 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(T 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:
// customed functions for selected problems (P3975)
void dfs(valueType u, ValueMatrix const &ParentTree, ValueVector &Ans) {
for (auto const &v : ParentTree[u]) {
dfs(v, ParentTree, Ans);
Ans[u] += Ans[v];
}
}
void calcEndPosCount(valueType type) {
EndPosCount.resize(2 * N + 1, 0);
if (type == 0) {
std::fill(EndPosCount.begin(), EndPosCount.end(), 1);
EndPosCount[0] = 0;
return;
}
ValueMatrix ParentTree(2 * N + 1);
for (valueType i = 1; i <= UsedPoolSize; ++i) {
ParentTree[Link[i]].push_back(i);
if (!Cloned[i])
EndPosCount[i] = 1;
}
dfs(0, ParentTree, EndPosCount);
EndPosCount[0] = 0;
}
void calcSubStrCount(valueType type) {
SubStrCount.resize(2 * N + 1, 1);
ValueVector TopologicalOrder;
TopologicalOrder.reserve(UsedPoolSize + 1);
ValueVector InDegree(UsedPoolSize + 1, 0);
for (valueType i = 1; i <= UsedPoolSize; ++i)
for (auto const &v : Transfer[i])
if (v != 0)
++InDegree[v];
std::queue<valueType> Queue;
for (valueType i = 0; i <= UsedPoolSize; ++i)
if (InDegree[i] == 0)
Queue.push(i);
while (!Queue.empty()) {
valueType const u = Queue.front();
Queue.pop();
TopologicalOrder.push_back(u);
for (auto const &v : Transfer[u]) {
if (v != 0) {
SubStrCount[v] += SubStrCount[u];
if (--InDegree[v] == 0)
Queue.push(v);
}
}
}
std::reverse(TopologicalOrder.begin(), TopologicalOrder.end());
SubStrCount = EndPosCount;
for (auto const &u : TopologicalOrder)
for (auto const &v : Transfer[u])
if (v != 0)
SubStrCount[u] += SubStrCount[v];
}
std::string Ans(valueType k) {
if (k > SubStrCount[0])
return "-1";
std::string Ans;
valueType u = 0;
while (k > 0) {
if (k <= EndPosCount[u])
break;
k -= EndPosCount[u];
for (T ch = 0; ch < CharSetSize; ++ch) {
if (Transfer[u][ch] == 0)
continue;
valueType const v = Transfer[u][ch];
if (k > SubStrCount[v]) {
k -= SubStrCount[v];
} else {
Ans.push_back(ch + 'a');
u = v;
break;
}
}
}
return Ans;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
string S;
std::cin >> S;
valueType type, K;
std::cin >> type >> K;
SuffixAutomaton<char> SAM(S.size());
for (auto const &ch : S)
SAM.extend(ch);
SAM.calcEndPosCount(type);
SAM.calcSubStrCount(type);
std::cout << SAM.Ans(K) << std::endl;
return 0;
}
[SDOI2008] Sandy 的卡片 / LCS - Longest Common Substring / LCS2 - Longest Common Substring II / LONGCS - Longest Common Substring
首先考虑如何求两个字符串的 \(\tt{LCS}\),设两个字符串为 \(S\) 和 \(T\),那么可以建出 \(S\) 的 \(\tt{SAM}\)。然后在其中匹配 \(T\) 并维护答案即可。
若存在多个串,那么可以类比两个串的思路,将最短串建出 \(\tt{SAM}\),然后在其中匹配其他串并维护答案。
具体的,对于同一每个字符串需要对于每个节点维护到达该节点的最长匹配长度,匹配结束后每个节点向其 \(\operatorname{Link}\) 更新最大值。同时需要维护所有串匹配到当前节点的最大匹配长度的最小值,所有节点的最小值的最大值即为答案。
Code of Sandy 的卡片
#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:
typedef std::map<valueType, valueType> TransferMap;
typedef std::vector<TransferMap> TransferMatrix;
private:
// main data
valueType N, UsedPoolSize;
ValueVector Link, Length;
TransferMatrix Transfer;
bitset Cloned;
// maybe useful
ValueVector TopoOrder;
// customed
ValueVector max, min;
// 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), max(2 * N + 1, 0), min(2 * N + 1, std::numeric_limits<valueType>::max()), Last(0) {
Link[0] = -1;
}
explicit SuffixAutomaton(ValueVector const &s) : SuffixAutomaton(s.size()) {
for (auto const &ch : s)
extend(ch);
}
void extend(valueType ch) {
valueType New = ++UsedPoolSize;
Length[New] = Length[Last] + 1;
valueType P = Last;
Last = New;
while (P != -1 && !Transfer[P].count(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 (P2463)
void Match(ValueVector const &s) {
std::fill(max.begin(), max.end(), 0);
valueType u = 0, l = 0;
for (auto ch : s) {
while (u > 0 && !Transfer[u].count(ch)) {
u = Link[u];
l = Length[u];
}
if (Transfer[u].count(ch)) {
u = Transfer[u][ch];
++l;
}
max[u] = std::max(max[u], l);
}
for (auto const &u : TopoOrder) {
if (Link[u] >= 0)
max[Link[u]] = std::min(std::max(max[Link[u]], max[u]), Length[Link[u]]);
min[u] = std::min(min[u], max[u]);
}
}
valueType Ans() const {
valueType ans = 0;
for (valueType i = 1; i <= UsedPoolSize; ++i)
if (min[i] != std::numeric_limits<valueType>::max())
ans = std::max(ans, min[i]);
return ans;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
valueType N;
std::cin >> N;
ValueMatrix A(N);
for (auto &a : A) {
valueType m;
std::cin >> m;
ValueVector b(m);
for (auto &c : b)
std::cin >> c;
a.resize(m - 1);
for (valueType i = 0; i < m - 1; ++i)
a[i] = b[i + 1] - b[i];
}
std::sort(A.begin(), A.end(), [](ValueVector const &a, ValueVector const &b) {
return a.size() < b.size();
});
SuffixAutomaton SAM(A.front());
SAM.GetTopoOrder();
for (valueType i = 1; i < N; ++i)
SAM.Match(A[i]);
std::cout << SAM.Ans() + 1 << std::endl;
return 0;
}