给一个字符串, 每次询问给两个数集 A, B, 求 A 中每个后缀和 B 中每个后缀的 LCP 长度之和.
所有询问 A 或 B 大小之和不超过 2 * 10^5.
首先想到暴力, 可以每次枚举所有数对, 在后缀树上两两求 LCA, 将 Len 统计入答案.
然后用树链剖分优化, 因为一个 LCA 的 Len 是它到根上的所有点的 Len 和父亲的 Len 的差的总和. 可以用树链剖分维护这个差加权的区间和. 对于每个 A 集合的点, 将它到根的所有点的权值加 1. 然后查询每个 B 集合的点到根的加权的差的和, 即可在 O(n + \log^2 n (\sum |A| + \sum |B|)) 的时间内求出答案.
所以需要支持区间删除, 单点修改, 全局查询, 复杂度 $O((n + |A| + |B|) \log n)$.
```cpp
unsigned ScdRk[200005], Bucket[200005], SA[200005], RK[200005];
unsigned RkTmp[400005], ST[18][200005], m, n;
unsigned SzA, SzB, A[200005], B[200005], C, D;
unsigned Bin[20], Log[200005];
unsigned long long Ans(0);
char a[200005];
struct Node {
Node* LS, * RS;
unsigned long long Sum, Cnt;
void Udt() {
this->Sum = this->LS->Sum + this->RS->Sum;
this->Cnt = this->LS->Cnt + this->RS->Cnt;
}
void PsDw() { if (!this->Cnt) this->LS->Cnt = this->RS->Cnt = this->LS->Sum = this->RS->Sum = 0; }
void Add(unsigned L, unsigned R) {
if (L == R) {
(this->Cnt) += D;
this->Sum = this->Cnt * L;
return;
}
this->PsDw();
register unsigned Mid((L + R) >> 1);
if (C <= Mid) { this->LS->Add(L, Mid); }
else { this->RS->Add(Mid + 1, R); }
this->Udt();
}
void QryDel(unsigned L, unsigned R) {
if (L > C) {
D += this->Cnt;
this->Cnt = this->Sum = 0;
return;
}
this->PsDw();
register unsigned Mid((L + R) >> 1);
if (Mid > C) this->LS->QryDel(L, Mid);
this->RS->QryDel(Mid + 1, R);
}
}N[400005], * CntN(N);
void Build(Node* x, unsigned L, unsigned R) {
if (L == R) { return; }
register unsigned Mid((L + R) >> 1);
Build(x->LS = ++CntN, L, Mid);
Build(x->RS = ++CntN, Mid + 1, R);
}
inline unsigned Min(unsigned L, unsigned R) {
register unsigned LenLog(Log[R - L + 1]);
return min(ST[LenLog][L], ST[LenLog][R - Bin[LenLog] + 1]);
}
signed main() {
n = RD(), m = RD();
scanf("%s", a + 1);
for (register unsigned i(1); i <= n; ++i)
++Bucket[RK[i] = (a[i] - 'a' + 2)];
for (register unsigned i(1); i <= 27; ++i)
Bucket[i] += Bucket[i - 1];
for (register unsigned i(1); i <= n; ++i)
SA[Bucket[RK[i]]--] = i;
for (register unsigned i(1), BucketTop(27); i <= n; i <<= 1) {
for (register unsigned j(n - i + 1); j <= n; ++j) ScdRk[j] = j;
for (register unsigned j(1), Top(n - i + 1); j <= n; ++j)
if (SA[j] > i) ScdRk[--Top] = SA[j] - i;
memset(Bucket, 0, (BucketTop + 1) << 2);
for (register unsigned j(1); j <= n; ++j)
++Bucket[RK[j]];
for (register unsigned j(1); j <= BucketTop; ++j)
Bucket[j] += Bucket[j - 1];
for (register unsigned j(1); j <= n; ++j)
SA[Bucket[RK[ScdRk[j]]]--] = ScdRk[j];
BucketTop = 0;
memcpy(RkTmp, RK, (n + 1) << 2);
for (register unsigned j(1); j <= n; ++j)
RK[SA[j]] = ((RkTmp[SA[j]] ^ RkTmp[SA[j - 1]]) || (RkTmp[SA[j] + i] ^ RkTmp[SA[j - 1] + i])) ? (++BucketTop) : BucketTop;
if (BucketTop == n) break;
}
for (register unsigned i(1), j, H(1); i <= n; ++i) {
if (RK[i] == 1) { ST[0][RK[i]] = 0; continue; }
j = SA[RK[i] - 1];if (H) --H;
while ((a[i + H] == a[j + H]) && (i + H <= n) && (j + H <= n)) ++H;
ST[0][RK[i]] = H;
}
for (register unsigned i(1), j(0); i <= n; i <<= 1, ++j)
Bin[j] = i, Log[i] = j;
for (register unsigned i(1); i <= n; ++i)
Log[i] = max(Log[i], Log[i - 1]);
for (register unsigned i(1), j(0); i <= n; i <<= 1, ++j)
for (register unsigned k(1); k + (i << 1) <= n + 1; ++k)
ST[j + 1][k] = min(ST[j][k], ST[j][k + i]);
B[0] = -1, A[0] = -1, Build(N, 0, n);
for (register unsigned i(1); i <= m; ++i) {
SzA = RD(), SzB = RD(), Ans = 0;
for (register unsigned i(1); i <= SzA; ++i) A[i] = RK[RD()];
for (register unsigned i(1); i <= SzB; ++i) B[i] = RK[RD()];
sort(A + 1, A + SzA + 1), sort(B + 1, B + SzB + 1);
for (register unsigned i(1), j(1); j <= SzB; ++j) {
C = Min(B[j - 1] + 1, B[j]), D = 0, N->QryDel(0, n), N->Add(0, n);
while ((A[i] <= B[j]) && (i <= SzA)) {
C = ((A[i] ^ B[j]) ? (Min(A[i] + 1, B[j])) : (n - SA[A[i]] + 1)), D = 1, N->Add(0, n);
++i;
}
Ans += N->Sum;
}
C = 0, N->QryDel(0, n);
for (register unsigned i(1), j(1); j <= SzA; ++j) {
C = Min(A[j - 1] + 1, A[j]), D = 0, N->QryDel(0, n), N->Add(0, n);
while ((B[i] < A[j]) && (i <= SzB)) {
C = Min(B[i] + 1, A[j]), D = 1, N->Add(0, n);
++i;
}
Ans += N->Sum;
}
printf("%llu\n", Ans);
}
return Wild_Donkey;
}
```
### [CF666E](https://www.luogu.com.cn/problem/CF666E)
一个母串 $S$, $n$ 个模式串 $T_i$, 每次询问子串 $S[l_1, r_1]$ 在 $T_i i\in[l_2, r_2]$ 中哪个模式串中出现最多.
因为今天是 SA 场, 所以感觉还是先介绍 SAM 做法比较礼貌.
首先建立 $GSAM$, 将离线下来的询问的子串对应的节点打上标记, 那么它会在它后缀树上子树上的所有节点代表的字符串中出现, 后缀树上每个点建线段树存储所属的字符串编号, 然后从下到上合并线段树, 每次遇到有标记的节点, 区间查询最大值即可.
好了这就是又简单又无脑的 SAM 做法, 接下来看恐怖的又难写又难想而且慢的 SA 做法.
将所有模式串加入 $S$ 后面, 中间插特殊字符, 然后求 $SA$, $h$.
离线所有询问, 将每个询问转化为求对于 $x \in [Head_{l_2}, Tail_{r_2}]$ 区间 $LCP_{l_1, x} \geq r_1 - l_1 + 1$ 的 $x$ 数量. 而 $LCP_{l_1, x}\geq r_1 - l_1 + 1$ 的 $Rank$ 一定是一段连续的区间, 并且可以二分, 然后用莫队维护在每个区间 $[Head_{l_2}, Tail_{r_2}]$ 中所有 $Rank$ 在对应区间中的 $x$ 属于每个模式串的数量 $Cnt$.
因为区间众数带一个值域约束, 所以对值域分块,
这样就能 $O(Len \log Len + q (\sqrt {Len} + \sqrt n))$ 解决问题了.
真的不懂, 明明可以 $\log Len$ 过得轻松写意, 非要 $\sqrt {Len}$ 把 $6s$ 的题跑出 $5.35s$ 的惊心动魄.
```cpp
unsigned ST[20][600005], Bin[20], Log[600005], ScdRk[600005];
unsigned Bucket[600005], SA[600005], RK[600005], RkTmp[1200005];
unsigned a[50005], BNum[600005], Cnt[50005], AnsN[500005], AnsC[500005];
unsigned m, n, q, A, B, C, D, t, Tmp(0), BlcL, BlcL2;
unsigned AnsBuc[230], BlcCnt[230][50005];
char S[600005];
struct Qry {
unsigned L, R, RL, RR, Bl, Num;
inline const char operator< (const Qry& x) const { return (this->Bl ^ x.Bl) ? (this->Bl < x.Bl) : (this->L < x.L); }
}Q[500005];
inline unsigned Min(unsigned L, unsigned R) {
register unsigned LenLog(Log[R - L + 1]);
return min(ST[LenLog][L], ST[LenLog][R - Bin[LenLog] + 1]);
}
inline void Add(unsigned x) {
if (!x) return;
register unsigned BlcN(x / BlcL2);
--BlcCnt[BlcN][Cnt[x]];
if (++Cnt[x] > AnsBuc[BlcN]) ++AnsBuc[BlcN];
++BlcCnt[BlcN][Cnt[x]];
}
inline void Del(unsigned x) {
if (!x) return;
register unsigned BlcN(x / BlcL2);
--BlcCnt[BlcN][Cnt[x]];
if (!BlcCnt[BlcN][Cnt[x]]) if ((Cnt[x]) == AnsBuc[BlcN]) --AnsBuc[BlcN];
++BlcCnt[BlcN][--Cnt[x]];
}
signed main() {
a[0] = 1;
scanf("%s", S + 1), a[1] = strlen(S + 1) + 1;
n = RD();
for (register unsigned i(1); i <= n; ++i) {
S[a[i]] = '`';
scanf("%s", S + a[i] + 1);
a[i + 1] = a[i] + strlen(S + a[i] + 1) + 1;
}
m = a[n + 1] - 1, BlcL2 = sqrt(n) + 1;
for (register unsigned i(1); i <= m; ++i) ++Bucket[RK[i] = (S[i] -= '_')];
for (register unsigned i(1); i <= 27; ++i) Bucket[i] += Bucket[i - 1];
for (register unsigned i(1); i <= m; ++i) SA[Bucket[RK[i]]--] = i;
for (register unsigned i(1), BucketSize(27); i <= m; ++i) {
memset(Bucket, 0, (BucketSize + 1) << 2);
for (register unsigned j(1); j <= m; ++j) ++Bucket[RK[j]];
for (register unsigned j(1); j <= BucketSize; ++j) Bucket[j] += Bucket[j - 1];
for (register unsigned j(m - i + 1); j <= m; ++j) ScdRk[j] = j;
for (register unsigned j(1), TopSR(m - i + 1); j <= m; ++j) if (SA[j] > i) ScdRk[--TopSR] = SA[j] - i;
for (register unsigned j(1); j <= m; ++j) SA[Bucket[RK[ScdRk[j]]]--] = ScdRk[j];
memcpy(RkTmp, RK, (m + 1) << 2), BucketSize = 0;
for (register unsigned j(1); j <= m; ++j) RK[SA[j]] = ((RkTmp[SA[j]] ^ RkTmp[SA[j - 1]]) || (RkTmp[SA[j] + i] ^ RkTmp[SA[j - 1] + i])) ? (++BucketSize) : (BucketSize);
if (BucketSize == m) break;
}
for (register unsigned i(0); i <= n; ++i)
for (register unsigned j(a[i]); j < a[i + 1]; ++j) BNum[RK[j]] = i;
for (register unsigned i(1), j(0); i <= m; ++i) {
if (j) --j;
if (RK[i] == 1) { ST[0][RK[i]] = 0; continue; }
while ((SA[RK[i] - 1] + j <= m) && (i + j <= m) && (S[i + j] == S[SA[RK[i] - 1] + j])) ++j;
ST[0][RK[i]] = j;
}
for (register unsigned i(1), j(0); i <= m; i <<= 1, ++j)
for (register unsigned k(1); k + (i << 1) - 1 <= m; ++k)
ST[j + 1][k] = min(ST[j][k], ST[j][k + i]);
for (register unsigned i(1), j(0); i <= m; i <<= 1, ++j) Bin[j] = i, Log[i] = j;
for (register unsigned i(1); i <= m; ++i) Log[i] = max(Log[i], Log[i - 1]);
q = RD(), BlcL = (m / sqrt(q)) + 1;
for (register unsigned i(1); i <= q; ++i) {
Q[i].RL = RD(), Q[i].RR = RD(), A = RD(), B = RD() - A + 1, A = RK[A], Q[i].Num = i;
register unsigned BL(2), BR(A), BMid;
while (BL < BR) {
BMid = (BL + BR) >> 1;
if (Min(BMid, A) >= B) BR = BMid;
else BL = BMid + 1;
}
Q[i].L = (ST[0][BL] < B) ? BL : (BL - 1), BL = A + 1, BR = m;
while (BL < BR) {
BMid = (BL + BR + 1) >> 1;
if (Min(A + 1, BMid) >= B) BL = BMid;
else BR = BMid - 1;
}
Q[i].R = (ST[0][BL] < B) ? (BL - 1) : BL;
}
for (register unsigned i(1); i <= n; ++i) Q[i].Bl = Q[i].R / BlcL;
sort(Q + 1, Q + q + 1);
for (register unsigned i(1), NowL(1), NowR(0), TmpAns, TmpPos, LBlc, RBlc, TmpL, TmpR; i <= q; ++i) {
while (NowL > Q[i].L) Add(BNum[--NowL]);
while (NowR < Q[i].R) Add(BNum[++NowR]);
while (NowL < Q[i].L) Del(BNum[NowL++]);
while (NowR > Q[i].R) Del(BNum[NowR--]);
LBlc = (Q[i].RL + BlcL2 - 1) / BlcL2, RBlc = (Q[i].RR + 1) / BlcL2, TmpAns = TmpPos = 0;
if (RBlc <= LBlc) {
for (register unsigned j(Q[i].RR); j >= Q[i].RL; --j) if (Cnt[j] >= TmpAns) TmpAns = Cnt[j], TmpPos = j;
AnsC[Q[i].Num] = TmpAns, AnsN[Q[i].Num] = TmpPos; continue;
}
for (register unsigned j(RBlc - 1); j >= LBlc; --j) if (AnsBuc[j] >= TmpAns) TmpAns = AnsBuc[j], TmpPos = j;
TmpL = TmpPos * BlcL2, TmpR = (TmpPos + 1) * BlcL2;
for (register unsigned j(TmpL); j < TmpR; ++j) if (Cnt[j] == TmpAns) { TmpPos = j; break; }
for (register unsigned j((LBlc* BlcL2) - 1); j >= Q[i].RL; --j) if (Cnt[j] >= TmpAns) TmpAns = Cnt[j], TmpPos = j;
for (register unsigned j((RBlc* BlcL2)); j <= Q[i].RR; ++j) if (Cnt[j] > TmpAns) TmpAns = Cnt[j], TmpPos = j;
AnsC[Q[i].Num] = TmpAns, AnsN[Q[i].Num] = TmpPos;
}
for (register unsigned i(1); i <= q; ++i) printf("%u %u\n", AnsN[i], AnsC[i]);
return Wild_Donkey;
}
```
### [CF1063F](https://www.luogu.com.cn/problem/CF1063F)
将字符串 $S$ 划分成 $m$ 个不相交的子串 $T_i$, 使得从左到右排序后 $T_i$ 是 $T_{i - 1}$ 的严格子串 (不是它本身的子串), 求这个最大的 $m$.
容易发现一个显然的性质: $Len_{T_i} = Len_{T_{i + 1}} + 1$ 一定不会使答案更劣.
所以 $m$ 是 $\sqrt{n}$ 规模的.
设 $f_i$ 表示以 $S_i$ 开头的子串作为 $T_1$, $m$ 的最大值. 可以发现 $f_i \leq f_{i + 1} + 1$, 这是因为如果 $f_i = f_{i + 1} + 2$, 那么将 $f_i$ 方案中的所有子串 $T$ 都删掉一个字符, 得到了 $f_{i + 1} + 1$ 个子串也是合法的, 所以这时 $f_{i + 1}$ 应该是原来的 $f_{i + 1} + 1$.
于是可以发现在 $f_{i - 1}$ 确定的情况下, $f_i$ 有 $f_{i - 1} + 1$ 种可能的取值, 即自然数 $[1, f_{i - 1} + 1]$.
我们只要二分所有可能的取值 $x$, 然后判断满足 $j \geq i + x$ 的 $LCP_{i, j} \geq x - 1$ 的 $j$ 是否满足 $f_j \geq x - 1$ 即可.
发现完全没必要二分, 因为每个 $f_i$ 最多比 $f_{i + 1}$ 大 $1$, 所以即使倒序枚举所有 $x$, 也不过是均摊 $O(n)$ 次判断而已.
对于判断, 如果直接枚举是 $O(n)$ 的,尝试优化.
发现对 $j$ 的约束 $j >= i + x$, 因为是倒序枚举 $x$, 并且随着 $i$ 的减小, $x$ 最多增加 $1$, 所以每次判断时 $i + x$ 是单调不增的.
利用这个性质, 我们可以每次移动 $i + x$ 之前, 将 $i + x$ 的 $f$ 值插入线段树的 $RK_{i + x}$ 位置中. 然后二分出 $LCP_{i, j} >= x - 1$ 的区间, 在线段树上区间查询最大值, 判断这个最大值即可, 每次判断 $O(\log n)$.
所以总复杂度是 $O(n \log n)$.
```cpp
unsigned SA[500005], RK[500005], ScdRk[500005], Bucket[500005], RkTmp[1000005];
unsigned ST[20][500005], Bin[20], Log[500005], f[500005];
unsigned m, n, Cnt(0), A, B, C, D, t, Ans(0), Final(1), Tmp(0);
char aP[500010], * a(aP);
unsigned Min(const unsigned L, const unsigned R) {
unsigned LenLog(Log[R - L + 1]);
return min(ST[LenLog][L], ST[LenLog][R - Bin[LenLog] + 1]);
}
struct Node {
Node* LS, * RS;
unsigned Val;
void Add(unsigned L, unsigned R) {
if (L == R) { this->Val = B;return; }
unsigned Mid((L + R) >> 1);
if (A <= Mid) this->LS->Add(L, Mid);
else this->RS->Add(Mid + 1, R);
this->Val = max(this->LS->Val, this->RS->Val);
}
void Qry(unsigned L, unsigned R) {
if ((A <= L) && (R <= B)) { Ans = max(Ans, this->Val); return; }
unsigned Mid((L + R) >> 1);
if (A <= Mid) this->LS->Qry(L, Mid);
if (Mid < B) this->RS->Qry(Mid + 1, R);
}
}N[1000005], * CntN(N);
void Build(Node* x, unsigned L, unsigned R) {
if (L == R) { return; }
unsigned Mid((L + R) >> 1);
Build(x->LS = (++CntN), L, Mid), Build(x->RS = (++CntN), Mid + 1, R);
}
signed main() {
n = RD();
fread(aP + 1, 1, n + 5, stdin), Build(N, 1, n);
while (a[1] < 'a') ++a;
for (unsigned i(1); i <= n; ++i) ++Bucket[RK[i] = (a[i] -= '`')];
for (unsigned i(1); i <= 26; ++i) Bucket[i] += Bucket[i - 1];
for (unsigned i(1); i <= n; ++i) SA[Bucket[RK[i]]--] = i;
for (unsigned i(1), BucketSize(26); i <= n; i <<= 1) {
memset(Bucket, 0, (BucketSize + 1) << 2);
for (unsigned j(1); j <= n; ++j) ++Bucket[RK[j]];
for (unsigned j(1); j <= BucketSize; ++j) Bucket[j] += Bucket[j - 1];
for (unsigned j(n - i + 1); j <= n; ++j) ScdRk[j] = j;
for (unsigned j(1), TopSR(n - i + 1); j <= n; ++j) if (SA[j] > i) ScdRk[--TopSR] = SA[j] - i;
for (unsigned j(1); j <= n; ++j) SA[Bucket[RK[ScdRk[j]]]--] = ScdRk[j];
memcpy(RkTmp, RK, (n + 1) << 2), BucketSize = 0;
for (unsigned j(1); j <= n; ++j) RK[SA[j]] = ((RkTmp[SA[j]] ^ RkTmp[SA[j - 1]]) || (RkTmp[SA[j] + i] ^ RkTmp[SA[j - 1] + i])) ? (++BucketSize) : (BucketSize);
if (BucketSize == n) break;
}
for (unsigned i(1), j(0); i <= n; ++i) {
if (RK[i] == 1) ST[0][1] = 0;
if (j) --j;
while (a[i + j] == a[SA[RK[i] - 1] + j]) ++j;
ST[0][RK[i]] = j;
}
for (unsigned i(1), j(0); i <= n; i <<= 1, ++j)
for (unsigned k(1); k + (i << 1) - 1 <= n; ++k)
ST[j + 1][k] = min(ST[j][k], ST[j][k + i]);
for (unsigned i(1), j(0); i <= n; i <<= 1, ++j) Bin[j] = i, Log[i] = j;
for (unsigned i(1); i <= n; ++i) Log[i] = max(Log[i], Log[i - 1]);
for (unsigned i(n), j(0), Right(n + 1); i; --i, ++j) {
while (j) {
unsigned BL(1), BR(RK[i]), BMid;
while (BL < BR) {
BMid = (BL + BR) >> 1;
if (Min(BMid, RK[i]) >= j) BR = BMid;
else BL = BMid + 1;
}
A = (ST[0][BL] >= j) ? (BL - 1) : (BL), BL = RK[i] + 1, BR = n;
while (BL < BR) {
BMid = (BL + BR + 1) >> 1;
if (Min(RK[i] + 1, BMid) >= j) BL = BMid;
else BR = BMid - 1;
}
B = (ST[0][BL] >= j) ? (BL) : (BL - 1);
Ans = 0, N->Qry(1, n);
if (Ans >= j) { Final = max(Final, f[i] = j + 1); break; }
BL = 1, BR = RK[i + 1];
while (BL < BR) {
BMid = (BL + BR) >> 1;
if (Min(BMid, RK[i + 1]) >= j) BR = BMid;
else BL = BMid + 1;
}
A = (ST[0][BL] >= j) ? (BL - 1) : (BL), BL = RK[i + 1] + 1, BR = n;
while (BL < BR) {
BMid = (BL + BR + 1) >> 1;
if (Min(RK[i + 1] + 1, BMid) >= j) BL = BMid;
else BR = BMid - 1;
}
B = (ST[0][BL] >= j) ? (BL) : (BL - 1);
Ans = 0, N->Qry(1, n);
if (Ans >= j) { Final = max(Final, f[i] = j + 1); break; }
A = RK[i + j], B = f[i + j], --j, N->Add(1, n);
}
if (!f[i]) f[i] = 1;
}
printf("%u\n", Final);
return Wild_Donkey;
}
```
## Day12: SAM & GSAM
### 后缀树
一棵压缩的 Trie, Trie 中存了所有后缀, 并且将 Trie 中的链压缩成一个点.
构造 Trie 的同时, 连接 $go_{x, c}$, 指向点 $x$ 的后缀前面加一个字符 $c$ 得到的字符串所在的节点. 这样就能得到一个后缀自动机.
### SAM
过水已隐藏
### 求第 $k$ 大子串
在后缀自动机上记录 $Size$ 然后在转移边上拓扑排序跑 DP.
### 求所有后缀的 LCP 总和
每个节点存自己子树中前缀结点个数 $Size$, 然后对每个点 $i$, 枚举它所有儿子, 对于儿子 $j$ 统计 $(Size_i - Size_j) * Size_j * Len_i$ 即可, 复杂度 $O(Son_x)$.
### 求长度 $k$ 的子串出现次数最大值
建立 SAM, BFS 到深度为 $k$, 然后统计到达次数即可.
### [CF700E](https://www.luogu.com.cn/problem/CF700E)
一个字符串对另一个字符串是好的, 当且仅当这个字符串在另一个字符串中出现了两次.
求对于一个 $S_1$, 最长有多少字符串满足 $S_{i + 1}$ 对 $S_i$ 是好的.
建立 $S_1$ 的 SAM. 因为最优解中, 一定是 $S_1$ 的极大公共非自身前后缀 (也就是 Border) 是 $S_2$, $S_2$ 的 $Border$ 是 $S_3$.
而很显然, 从 $S_1$ 到 $S_k$ 所在的节点一定在后缀树上的一条链上.
然后就可以对 $S_i$ 在后缀树上倍增找 $S_{i + 1}$ 了. 每个点 $i$ 记录一个 $f_i$, 表示它做 $S_1$ 的最大 $k$ 值.
对于每个点 $i$, 用线段树合并处理出它的 $EndPos$ 集合, 以及它集合中最右边的元素 $Right$, 然后倍增查询最深的满足 $EndPos$ 在 $[Right_i - Len_i + Len_{Fa_j} - 1, Right_i)$ 存在元素的祖先 $j$, 则 $f_i = f_j + 1$.
```cpp
unsigned m, n, Cnt(0), Final, A, B, C, D, t, Ans(0), Tmp(0);
char aP[200005], * a(aP);
struct Seg {
Seg* LS, * RS;
char Val;
}S[10000005], * CntS(S);
void Insert(Seg* x, unsigned L, unsigned R) {
x->Val = 1;
if (L == R) { x->Val = 1; return; }
unsigned Mid((L + R) >> 1);
if (A <= Mid) Insert(x->LS = ++CntS, L, Mid);
else Insert(x->RS = ++CntS, Mid + 1, R);
}
void Qry(Seg* x, unsigned L, unsigned R) {
if ((A <= L) && (R <= B)) { Ans |= x->Val; return; }
unsigned Mid((L + R) >> 1);
if (A <= Mid) if (x->LS) Qry(x->LS, L, Mid);
if (B > Mid) if (x->RS) Qry(x->RS, Mid + 1, R);
}
Seg* Merge(Seg* x, Seg* y, unsigned L, unsigned R) {
unsigned Mid((L + R) >> 1);
if (y->LS) {
if (x->LS) { if (x->LS < x) *(++CntS) = *(x->LS), x->LS = CntS; x->LS = Merge(x->LS, y->LS, L, Mid); }
else x->LS = ++CntS, * CntS = *(y->LS);
}
if (y->RS) {
if (x->RS) { if (x->RS < x) *(++CntS) = *(x->RS), x->RS = CntS; x->RS = Merge(x->RS, y->RS, Mid + 1, R); }
else x->RS = ++CntS, * CntS = *(y->RS);
}
if (x->LS) x->Val |= x->LS->Val;
if (x->RS) x->Val |= x->RS->Val;
return x;
}
struct Node {
Node* Fa[18], * Bro, * Son, * To[26];
Seg* Root;
unsigned Len, Right, f;
}N[400005], * CntN(N), * Last(N);
void Add(const char x) {
Last->To[x] = ++CntN, CntN->Len = Last->Len + 1;
Node* Back(Last->Fa[0]);
Last = CntN;
while (Back) {
if (Back->To[x]) { break; }
Back->To[x] = Last;
Back = Back->Fa[0];
}
if (!Back) Last->Fa[0] = N;
else {
if (Back->To[x]->Len == Back->Len + 1) Last->Fa[0] = Back->To[x];
else {
Node* Bfr(Back->To[x]);
(++CntN)->Fa[0] = Bfr->Fa[0], CntN->Len = Back->Len + 1, Bfr->Fa[0] = CntN, Last->Fa[0] = CntN;
memcpy(CntN->To, Bfr->To, sizeof(Bfr->To));
while (Back) {
if (Back->To[x] == Bfr) Back->To[x] = CntN;
else break;
Back = Back->Fa[0];
}
}
}
}
void DFS1(Node* x) {
Node* Now(x->Son);
if (!(x->Root)) x->Root = ++CntS;
while (Now) {
for (char i(0); Now->Fa[i]; ++i) Now->Fa[i + 1] = Now->Fa[i]->Fa[i];
DFS1(Now);
x->Root = Merge(x->Root, Now->Root, 1, n);
x->Right = max(x->Right, Now->Right);
Now = Now->Bro;
}
}
void DFS2(Node* x) {
Node* Now(x->Son), * Jump(x);
for (char i(17); i >= 0; --i) {
if (Jump->Fa[i] > N) {
Ans = 0, A = x->Right - x->Len + Jump->Fa[i]->Fa[0]->Len + 1, B = x->Right - 1, Qry(Jump->Fa[i]->Root, 1, n);
if (!Ans) Jump = Jump->Fa[i];
}
}
if (Jump->Fa[0]) Final = max(Final, x->f = Jump->Fa[0]->f + 1);
while (Now) {
DFS2(Now);
Now = Now->Bro;
}
}
signed main() {
n = RD();
fread(a + 1, 1, n + 3, stdin);
while (a[1] < 'a') ++a;
for (unsigned i(1); i <= n; ++i) Add(a[i] -= 'a'), A = i, Insert(Last->Root = ++CntS, 1, n), Last->Right = i;
for (Node* i(N + 1); i <= CntN; ++i) i->Bro = i->Fa[0]->Son, i->Fa[0]->Son = i;
DFS1(N), DFS2(N);
printf("%u\n", Final);
return Wild_Donkey;
}
```
### 倍增找子串所在的节点
保存每个后缀所在的节点, $Pos_i$ 表示的子串是 $[1, i]$.
找子串 $[a, b]$ 倍增找 $Pos_b$ 的祖先 $i$, 使得 $b - a + 1 \in (Len_{Fa_i}, Len_i]$.
### [TJOI2016](https://www.luogu.com.cn/problem/P4094)
给一个字符串, 每次询问子串 $[a, b]$ 的子串和 $[c, d]$ 的 LCP 长度.
> 注意, 这里并不是求 $[a, b]$ 的子串和 $[c, d]$ 的子串的 LCP, 而是求 $[a, b]$ 的子串和 $[c, d]$ 本身的 LCP, 我一开始读错题了, 以至于无论如何都是 $O(qn\log n)$.
建立后缀自动机, 记 $Pos_i$ 为前缀 $[1, i]$ 对应的节点的指针.
二分答案, 判断 LCP 是 $x$ 时是否成立.
如果 LCP 大于等于 $x$, 则子串 $[c, c + x - 1]$ 一定是 $[a, b]$ 的子串.
这样就把二分答案的判断转化为了查询一个字符串是否是另一个字符串的子串的问题.
我们可以倍增找到 $[c, c + x - 1]$ 所在的节点, 判断它 $EndPos$ 集合中 (也就是一些说法中的 $Right$ 集合), 区间 $[a + x - 1, b]$ 中是否有值. 如果有, 说明它在对应的地方出现并且被 $[a, b]$ 完全包含.
对于 $EndPos$ 的计算, 区间查询, 就用线段树合并来解决. 但是这里的线段树合并和前面链接中提到的线段树合并的不同之处在于: 这里线段树合并之后还是有用的, 需要保护原树信息不被破坏, 而模板中的线段树合并之后不会访问, 所以只需要保证合并后的新树是正确的.
线段树合并, 对于后缀树上 $EndPos$ 合并的问题, 线段树合并的时空复杂度是 $O(\log n)$ 的, 接下来是证明:
首先一开始会在每个前缀所在的节点的线段树中插入一个值, 一共是 $n$ 个节点, 插入需要 $O(\log n)$ 的时空.
接下来是合并:
对于本问题, 只有合并的两棵线段树的交, 才会新建一个点, 而两棵线段树的并就是合并后的线段树. 定义一个点的 $Size$ 是它子树中叶子的数量.
通过链接中对 $EndPos$ 集合的几个性质的介绍, 我们知道合并的两棵线段树的叶子的交为 $0$.
两树的交中, 找出位置相同的两个点, $x$, $y$, 假设我们把 $y$ 的信息合并到 $x$ 上, 这时需要对 $x$ 新建一个点 $x'$ 存储两点合并后的信息, 然后将 $x'$ 接到 $x$ 的父亲上.
那么 $Size_{x'}$ 就是 $Size_x + Size_y$, 因为 $Size_y > 0$, 所以 $Size_{x'}$ 一定大于 $Size_x$. 对于 $x$ 所在的这个位置, 一共需要的点的数量最多就是这个位置在合并满的线段树上的 $Size$.
对于线段树上的每一层, $Size$ 之和都是 $n$. 所以每一层需要的点数之和就是 $n$, 线段树一共有 $O(\log n)$ 层, 所以一共需要 $O(n \log n)$ 个点. 因为每次执行 $Merge$ 操作都是在新建节点之后, 所以时间复杂度等于空间复杂度.
最后是查询, 因为每次二分答案判断时需要 $O(\log n)$ 地倍增找对应节点, 也需要 $O(\log n)$ 对线段树进行区间查询, 所以一次询问的复杂度是 $O(\log^2n)$.
加上一开始的构造自动机的 $O(n)$ 和初始化倍增数组的 $O(n \log n)$, 本题总复杂度 $O(n \log n + q \log^2 n)$.
```cpp
unsigned m, n, Cnt(0), A, B, C, D, Ans(0), QrL, QrR;
char aP[100005], * a(aP), Tmp(0);
struct Seg {
Seg* LS, * RS;
}S[5000005], * CntS(S);
void Insert(Seg* x, unsigned L, unsigned R) {
if (L == R) return;
unsigned Mid((L + R) >> 1);
if (A <= Mid) Insert(x->LS = ++CntS, L, Mid);
else Insert(x->RS = ++CntS, Mid + 1, R);
}
void Qry(Seg* x, unsigned L, unsigned R) {
if ((QrL <= L) && (R <= QrR)) { Tmp |= 1; return; }
unsigned Mid((L + R) >> 1);
if ((QrL <= Mid) && (x->LS)) Qry(x->LS, L, Mid);
if (Tmp) return;
if ((Mid < QrR) && (x->RS)) Qry(x->RS, Mid + 1, R);
}
void Merge(Seg* x, Seg* y, unsigned L, unsigned R) {
unsigned Mid((L + R) >> 1);
if (y->LS) {
if (x->LS) *(++CntS) = *(x->LS), x->LS = CntS, Merge(CntS, y->LS, L, Mid);
else x->LS = y->LS;
}
if (y->RS) {
if (x->RS) *(++CntS) = *(x->RS), x->RS = CntS, Merge(CntS, y->RS, Mid + 1, R);
else x->RS = y->RS;
}
}
struct Node {
Node* To[26], * Son, * Bro, * Fa[16];
Seg* Root;
unsigned Len;
}N[200005], * CntN(N), * Last(N), * Pos[100005];
void Add(const char x) {
(++CntN)->Len = Last->Len + 1;
Node* Back(Last);
Last = CntN;
while (Back) {
if (!(Back->To[x])) Back->To[x] = Last;
else break;
Back = Back->Fa[0];
}
if (!Back) Last->Fa[0] = N;
else {
if (Back->Len + 1 == Back->To[x]->Len) Last->Fa[0] = Back->To[x];
else {
Node* Bfr(Back->To[x]);
/*注意这里, Root 也会被复制, 要记得清除, 调了一上午的教训*/
*(++CntN) = *Bfr, Bfr->Fa[0] = CntN, Last->Fa[0] = CntN, CntN->Len = Back->Len + 1, CntN->Root = NULL;
while (Back) {
if (Back->To[x] == Bfr) Back->To[x] = CntN;
else break;
Back = Back->Fa[0];
}
}
}
}
void DFS(Node* x) {
Node* Now(x->Son);
if (!(x->Root)) x->Root = ++CntS;
while (Now) {
for (int i = 0; Now->Fa[i]; ++i) Now->Fa[i + 1] = Now->Fa[i]->Fa[i];
DFS(Now);
Merge(x->Root, Now->Root, 1, n);
Now = Now->Bro;
}
}
signed main() {
n = RD(), m = RD(), scanf("%s", a + 1), Pos[0] = N;
while (a[1] < 'a') ++a;
for (unsigned i(1); i <= n; ++i) Add(a[i] -= 'a'), Pos[i] = Last, A = i, Insert(Last->Root = ++CntS, 1, n);
for (Node* i(N + 1); i <= CntN; ++i) i->Bro = i->Fa[0]->Son, i->Fa[0]->Son = i;
DFS(N);
for (unsigned i(1); i <= m;++i) {
A = RD(), B = RD(), C = RD(), D = RD();
unsigned BL(1), BR(min(D - C + 1, B - A + 1)), BMid;
while (BL ^ BR) {
BMid = (BL + BR + 1) >> 1;
Node* Jump(Pos[C + BMid - 1]);
for (char i(15); i >= 0; --i) if ((Jump->Fa[i]) && (Jump->Fa[i]->Len >= BMid)) Jump = Jump->Fa[i];
Tmp = 0, QrL = A + BMid - 1, QrR = B, Qry(Jump->Root, 1, n);
if (Tmp) BL = BMid;
else BR = BMid - 1;
}
printf("%u\n", BL);
}
// system("pause");
return Wild_Donkey;
}
```
### [TJOI2015](https://www.luogu.com.cn/problem/P3975)
给一个字符串, 询问字典序第 $k$ 小的本质不同的子串, 询问字典序第 $k$ 小的子串.
建立 SAM, DP 求出每个点求 $Size$ 表示这个点转移边能转移到多少子串, 然后 DFS.
在转移边连接的 DAG 上, DP 出 $Size$. 表示从原点出发, 经过点 $i$ 的所有路径所代表的字符串在查询中算多少种子串, DP 方程:
$$
Size_i = Val_i + \sum_{j = 'a'}^{'z'} Size_{i_{To_j}}
$$
关于 $Val_i$, 它的意义是从源点到 $i$ 点的路径, 以 $i$ 点结束, 在查询种算作几种子串. 很显然空串不能算入答案, 所以原点的 $Val$ 为 $0$, 而其它点就要分类讨论了.
对于第一种询问, 本质相同的两个子串算作一个, 所以显然 $Val_i = 1$, 对于第二种询问, $Val_i$ 就是从原点到 $i$ 的任意一条路径代表的字符串, 在原串中出现的次数, 所以这个值就是 $i$ 在后缀树上的子树中, 前缀节点 (也就是 SAM 主链上的节点) 的数量. 在后缀树上 DFS 即可.
```cpp
unsigned m, n, Cnt(0), Hd(0), Tl(0), A, B, C, D, t, Ans(0), Tmp(0);
char AddC, Opt;
struct Node {
Node* To[26], * Fail, * Son, * Bro;
unsigned long long Size;
unsigned Len, EndPos, Idg;
}N[1000005], * Topo[1000005], * CntN(N), * Last(N);
void Add() {
Node* Back(Last);
Last = ++CntN, Last->Len = Back->Len + 1, Last->EndPos = 1;
while (Back && (!(Back->To[AddC]))) { Back->To[AddC] = Last, Back = Back->Fail; }
if (Back) {
if (Back->To[AddC]->Len ^ (Back->Len + 1)) {
Node* Bfr(Back->To[AddC]);
*(++CntN) = *Bfr, CntN->Len = Back->Len + 1, Bfr->Fail = Last->Fail = CntN, CntN->EndPos = 0;
while (Back && (Back->To[AddC] == Bfr)) Back->To[AddC] = CntN, Back = Back->Fail;
}
else Last->Fail = Back->To[AddC];
}
else Last->Fail = N;
}
void DFS(Node* x) {
Node* Now(x->Son);
while (Now) DFS(Now), x->EndPos += Now->EndPos, Now = Now->Bro;
}
void Qry(Node* x) {
if (x->EndPos >= A) { putchar('\n'); return; } A -= x->EndPos;
for (char i(0); i < 26; ++i) if (x->To[i]) { if (x->To[i]->Size >= A) { putchar(i + 'a'); return Qry(x->To[i]); } A -= x->To[i]->Size; }
}
signed main() {
AddC = getchar();
while (AddC < 'a') AddC = getchar();
while (AddC >= 'a') AddC -= 'a', Add(), AddC = getchar();
for (Node* i(N); i <= CntN; ++i) for (char j(0); j < 26; ++j) if (i->To[j]) ++(i->To[j]->Idg);
Topo[++Tl] = N, n = CntN - N;
while (Hd ^ Tl) for (char i(!(++Hd)); i < 26; ++i) if (Topo[Hd]->To[i]) if (!(--(Topo[Hd]->To[i]->Idg))) Topo[++Tl] = Topo[Hd]->To[i];
Opt = RD(), A = RD();
if (Opt) {
for (Node* i(N + 1); i <= CntN; ++i) i->Bro = i->Fail->Son, i->Fail->Son = i;
DFS(N), N->EndPos = 0;
}
else for (Node* i(N + 1); i <= CntN; ++i) i->EndPos = 1;
for (unsigned i(n + 1); i; --i) {
Topo[i]->Size = Topo[i]->EndPos;
for (char j(0); j < 26; ++j) if (Topo[i]->To[j]) Topo[i]->Size += Topo[i]->To[j]->Size;
}
if (N->Size < A) { printf("-1\n"); return 0; }
Qry(N);
return Wild_Donkey;
}
```
### [NOI2018](https://www.luogu.com.cn/problem/P4770)
给一个字符串, 每次给一个模式串 $T$ 求有多少本质不同的子串不是 $S$ 的子串 $[l, r]$ 的子串.
对 $S$ 构造后缀自动机, 记录后缀树的 DFS 序和子树大小, 用以 DFSr 为序的线段树维护区间最大值, 用来查询每个点的 $EndPos$ 集合的最大值.
离线所有询问, 按 $r$ 排序, 然后对于每个询问 $i$, 保证 $\leq r_i$ 的 $Endpos$ 都被插入了, 这样就能查询一个点代表的子串在 $r_i$ 之前最后一次出现的位置.
对于询问的字符串 $T_i$, 从左到右从 $S$ 的 SAM 上识别, 维护当前考虑过的 $T_i$ 的前缀, 和 $S$ 的 $[l_i, r_i]$ 中的子串的 $LCS$ (Longest Common Suffix).
同时构造 $T_i$ 的 SAM, 在 SAM 中插入刚刚考虑的字符, 得到这个前缀的节点 $Now$, 求出 $\max(0, LCS - Now->Fail->Len)$, 统计得到 $T_i$ 和 $S$ 的 $[l_i, r_i]$ 子串的本质不同的公共子串数量.
最后在 $T_i$ 的 SAM 中统计本质不同的子串数, 减去本质不同公共子串数, 得到所求答案.
```cpp
unsigned long long Ans[100005];
unsigned m, n, Cnt(0), B, C, D, t, Calc;
unsigned LCS, Pointer(0), DFSCnt(0), Tmp(0);
char b[1000005], CTmp;
struct Ask {
unsigned Frm, To, L, R, AsNum;
inline const char operator <(const Ask& x) { return this->R < x.R; }
}A[100005];
struct Seg {
Seg* LS, * RS;
unsigned Mx;
}S[2000005], * CntS(S);
void Insert(Seg* x, unsigned L, unsigned R) {
x->Mx = max(x->Mx, C);
if (L == R) return;
unsigned Mid((L + R) >> 1);
if (B <= Mid) { if (!(x->LS)) x->LS = ++CntS; Insert(x->LS, L, Mid); }
else { if (!(x->RS)) x->RS = ++CntS; Insert(x->RS, Mid + 1, R); }
}
void Qry(Seg* x, unsigned L, unsigned R) {
if ((B <= L) && (R <= C)) { D = max(D, x->Mx);return; }
unsigned Mid((L + R) >> 1);
if ((B <= Mid) && (x->LS)) Qry(x->LS, L, Mid);
if ((Mid < C) && (x->RS)) Qry(x->RS, Mid + 1, R);
}
struct Node {
Node* To[26], * Fail, * Son, * Bro;
unsigned Len, DFSr, Size;
}N[2000005], * Prefix[500005], * CntN(N), * Last(N), * DelLine(N), * Search, * Choice;
void Add() {
Node* Back(Last);
Last = ++CntN, CntN->Len = Back->Len + 1, memset(Last->To, 0, sizeof(Last->To));
while (Back && (!(Back->To[CTmp]))) Back->To[CTmp] = Last, Back = Back->Fail;
if (Back) {
if ((Back->To[CTmp]->Len) ^ (Back->Len + 1)) {
Node* Bfr(Back->To[CTmp]);
*(++CntN) = *Bfr, CntN->Len = Back->Len + 1, Bfr->Fail = Last->Fail = CntN;
while (Back && (Back->To[CTmp] == Bfr)) Back->To[CTmp] = CntN, Back = Back->Fail;
}
else Last->Fail = Back->To[CTmp];
}
else Last->Fail = DelLine;
}
void DFS(Node* x) {
Node* Now(x->Son);
x->DFSr = ++DFSCnt, x->Size = 1;
while (Now) {
DFS(Now), x->Size += Now->Size, Now = Now->Bro;
}
}
signed main() {
CTmp = getchar(); while (CTmp < 'a') CTmp = getchar();
while (CTmp >= 'a') CTmp -= 'a', Add(), Prefix[++n] = Last, CTmp = getchar();
DelLine = Prefix[n] + 2, m = RD();
for (unsigned i(1); i <= m; ++i) {
A[i].Frm = Pointer + 1, CTmp = getchar(); while (CTmp < 'a') CTmp = getchar();
while (CTmp >= 'a') b[++Pointer] = CTmp - 'a', CTmp = getchar();
A[i].L = RD(), A[i].R = RD(), A[i].To = Pointer, A[i].AsNum = i;
}
for (Node* i(N + 1); i <= CntN; ++i) i->Bro = i->Fail->Son, i->Fail->Son = i;
A[m + 1].R = A[m].R, sort(A + 1, A + m + 1), DFS(N);
for (unsigned i(1); i <= A[1].R; ++i) B = Prefix[i]->DFSr, C = i, Insert(S, 1, Prefix[n] - N + 2);
for (unsigned i(1); i <= m; ++i) {
LCS = 0, Search = N, Last = CntN = DelLine, memset(DelLine, 0, sizeof(Node)), Tmp = 0;
for (unsigned j(A[i].Frm); j <= A[i].To; ++j) {
Calc = 0, Choice = NULL;
while (Search && (Search->Len >= Calc)) {
if (Search->To[b[j]]) {
B = Search->To[b[j]]->DFSr, C = B + Search->To[b[j]]->Size - 1, D = 0, Qry(S, 1, Prefix[n] - N + 2);
if (D >= A[i].L) {
if (D >= A[i].L + Search->Len) {
if (Calc < Search->Len + 1) Calc = Search->Len + 1, Choice = Search->To[b[j]]; break;
}
if (Calc < min(Search->Len + 1, D - A[i].L + 1))
Calc = min(Search->Len + 1, D - A[i].L + 1), Choice = Search->To[b[j]];
}
}
Search = Search->Fail;
}
Search = (Choice) ? Choice : (N + 1);
LCS = min((unsigned)LCS + 1, Calc), CTmp = b[j], Add(), Tmp += (Last->Fail->Len <= LCS) ? (LCS - Last->Fail->Len) : 0;
}
for (Node* j(DelLine + 1); j <= CntN; ++j) Ans[A[i].AsNum] += j->Len - j->Fail->Len;
Ans[A[i].AsNum] -= Tmp;
for (unsigned j(A[i].R + 1); j <= A[i + 1].R; ++j) B = Prefix[j]->DFSr, C = j, Insert(S, 1, Prefix[n] - N + 2);
}
for (unsigned i(1); i <= m; ++i) printf("%llu\n", Ans[i]);
return Wild_Donkey;
}
```
### GSAM
过水已隐藏
### [ZJOI2015](https://www.luogu.com.cn/problem/P3346)
给⼀个叶⼦数不超过 $20$ 的 Trie, 但是与一般 Trie 不同的是, 它可以将任意一条路径作为一个字符串, 而不是只有从上往下的路径, 求有多少个不同的字符串.
分别以每个叶子作为根, 并成一棵新的 Trie. BFS 新的 Trie, 建 GSAM. 然后统计本质不同子串数量.
```cpp
unsigned m, n, Cnt(0), A, B, t, Tmp(0);
unsigned Tl(0), Hd(0), Cls;
unsigned long long Ans;
struct Tr;
struct Edge {
Tr* To;
Edge* Nxt;
}E[200005], * CntE(E);
struct Tr {
Edge* Fst;
unsigned Deg;
char Cr;
}IT[100005];
struct Node {
Node* To[10], * Fail;
unsigned Len;
}N[4000005], * CntN(N), * Last(N);
struct Trie {
Trie* To[10];
Node* Am;
}T[2000005], * Q[2000005], * CntT(T);
void Link(Tr* x, Tr* y) { (++CntE)->Nxt = x->Fst, x->Fst = CntE, CntE->To = y, ++(x->Deg); }
void DFS(Tr* x, Tr* y, Trie* z) {
Edge* Sid(x->Fst);
if (!(z->To[x->Cr])) z->To[x->Cr] = ++CntT; z = z->To[x->Cr];
while (Sid) {
if (Sid->To != y) {
DFS(Sid->To, x, z);
}
Sid = Sid->Nxt;
}
}
Node* Add(char x) {
Node* Back(Last), * Cur(++CntN);
Cur->Len = Last->Len + 1;
while (Back) {
if (!(Back->To[x])) Back->To[x] = Cur;
else break;
Back = Back->Fail;
}
if (Back) {
if (Back->To[x]->Len == Back->Len + 1) {
Cur->Fail = Back->To[x];
}
else {
Node* Bfr(Back->To[x]);
*(++CntN) = *(Back->To[x]);
CntN->Len = Back->Len + 1, Bfr->Fail = Cur->Fail = CntN;
while (Back && (Back->To[x] == Bfr)) Back->To[x] = CntN, Back = Back->Fail;
}
}
else {
Cur->Fail = N;
}
return Cur;
}
signed main() {
n = RD(), Cls = RD();
for (unsigned i(1); i <= n; ++i) IT[i].Cr = RD();
for (unsigned i(1); i < n; ++i)
A = RD(), B = RD(), Link(IT + A, IT + B), Link(IT + B, IT + A);
for (unsigned i(1); i <= n; ++i) if (IT[i].Deg == 1) DFS(IT + i, NULL, T);
Q[++Tl] = T, T->Am = N;
while (Tl ^ Hd) {
Trie* Now(Q[++Hd]);
Last = Now->Am;
for (unsigned i(0); i < Cls; ++i) if (Now->To[i]) Now->To[i]->Am = Add(i), Q[++Tl] = Now->To[i];
}
for (Node* i(N + 1); i <= CntN; ++i) Ans += i->Len - i->Fail->Len;
printf("%llu\n", Ans);
return Wild_Donkey;
}
```
### [BZOJ4545](https://darkbzoj.tk/problem/4545)
给一棵 Trie, 支持三种操作:
- 求本质不同的子串数量
- 插入一个子树
- 询问一个字符串出现了多少次
这是第一次写在线 GSAM, 确实遇到不少细节.
求本质不同子串数量可以在插入过程中, 直接维护一个全局变量.
而第三种操作, 需要询问一个点后缀树上子树信息和, 普遍的做法是用 LCT 在线维护.
但是有一种离线做法, 仅用树状数组就可以回答询问, 首先我们知道在插入过程中, 一个点后缀树上的点只会增加不会减少, 所以在构造完之后, DFS 后缀树, 然后对每个点记录 $Size$, 那么它的子树的 DFS 序形成一个区间 $[DFSr_x, DFSr_x + Size_x - 1]$, 记录构造过程中, 对点的权值修改的操作序列, 用树状数组维护单点增加, 区间求和即可.
```cpp
unsigned long long Ans[100005], Sum;
unsigned Map[100005], Qry[100005][3], Edit[1000005], CntE(0);
unsigned m, n, DFSCnt(0), Cnt(0);
unsigned A, B, D, t, Tmp(0);
unsigned TrArray[200005], ULimit;
char C;
void Chg(unsigned x) { while (x <= ULimit) ++TrArray[x], x += Lowbit(x); }
void Que(unsigned x) { while (x) Tmp += TrArray[x], x -= Lowbit(x); }
struct Node {
Node* To[26], * Fail, * Bro, * Son;
unsigned Len, DFSr, Size;
}N[200005], * CntN(N), * Search, * Last;
void Add() {
if (Last->To[C]) {
if (Last->To[C]->Len == Last->Len + 1) { Map[B] = Last->To[C] - N;return; }
Node* Bfr(Last->To[C]), * x(++CntN);
x->Fail = Bfr->Fail, Bfr->Fail = x, x->Len = Last->Len + 1, Map[B] = x - N;
for (char i(0); i < 26; ++i) if ((Bfr->To[i]) && (Bfr->To[i]->Len)) x->To[i] = Bfr->To[i];
while (Last && (Last->To[C] == Bfr)) Last->To[C] = x, Last = Last->Fail;
return;
}
Node* x(++CntN);
x->Len = Last->Len + 1, Map[B] = x - N;
while (Last) {
if (Last->To[C]) break; else Last->To[C] = x;
Last = Last->Fail;
}
if (Last) {
if (Last->To[C]->Len == Last->Len + 1) { x->Fail = Last->To[C], Sum += x->Len - Last->To[C]->Len; return; }
Node* Bfr(Last->To[C]);
(++CntN)->Len = Last->Len + 1, CntN->Fail = Bfr->Fail, Bfr->Fail = x->Fail = CntN, Sum += x->Len - CntN->Len;
for (char i(0); i < 26; ++i) if ((Bfr->To[i]) && (Bfr->To[i]->Len)) CntN->To[i] = Bfr->To[i];
while (Last && (Last->To[C] == Bfr)) Last->To[C] = CntN, Last = Last->Fail;
}
else x->Fail = N + 1, Sum += x->Len;
}
void DFS_Final(Node* x) {
x->DFSr = ++DFSCnt, x->Size = 1;
Node* Now(x->Son);
while (Now) DFS_Final(Now), x->Size += Now->Size, Now = Now->Bro;
}
signed main() {
RD(), n = RD(), memset(Ans, 0x3f, sizeof(Ans)), Map[1] = ++CntN - N;
for (register unsigned i(1); i < n; ++i) {
A = RD(), B = RD(), C = getchar(); while (C < 'a') C = getchar();
if (A > B) swap(A, B);
C -= 'a', Last = N + Map[A], Add(), Edit[++CntE] = Map[B];
}
m = RD();
for (unsigned i(1); i <= m; ++i) {
D = RD();
if (D == 1) Ans[i] = Sum;
else {
if (D & 1) {
C = getchar(), Search = N + 1;
while (C < 'a') C = getchar();
while (C >= 'a') {
if (!(Search->To[C - 'a'])) { Ans[i] = 0; while (C >= 'a') C = getchar(); break; }
Search = Search->To[C - 'a'], C = getchar();
}
if (Ans[i]) Qry[++Cnt][1] = Search - N, Qry[Cnt][0] = i, Qry[Cnt][2] = CntE;
}
else {
RD(), D = RD();
for (unsigned j(1); j < D; ++j) {
A = RD(), B = RD(), C = getchar(); while (C < 'a') C = getchar();
if (A > B) swap(A, B);
C -= 'a', Last = N + Map[A], Add(), Edit[++CntE] = Map[B];
}
}
}
}
for (Node* i(N + 2); i <= CntN; ++i) i->Bro = i->Fail->Son, i->Fail->Son = i;
DFS_Final(N + 1), ULimit = CntN - N;
for (unsigned i(1), j(1); i <= Cnt; ++i) {
while (j <= Qry[i][2]) { Chg(N[Edit[j]].DFSr), ++j; }
Tmp = 0, Que(N[Qry[i][1]].DFSr + N[Qry[i][1]].Size - 1), Ans[Qry[i][0]] = Tmp, Tmp = 0, Que(N[Qry[i][1]].DFSr - 1), Ans[Qry[i][0]] -= Tmp;
}
for (unsigned i(1); i <= m; ++i) if (Ans[i] < 0x3f3f3f3f3f3f3f3f) printf("%llu\n", Ans[i]);
return Wild_Donkey;
}
```
### 求多少子串是 $n$ 个字符串中至少 $k$ 个字符串的子串
建立 GSAM, 每个点存一个 $Cnt$ 记录多少个字符串存在这个点, 然后对后缀自动机进行 DP, 只走那些 $Cnt \geq k$ 的节点.
## Day13: 模拟赛
### A
给一个字符串, 判断它是否是一个字符串连续写两次后插入一个字符得到的, 如果可以构造并且唯一, 输出这个字符串.
分类讨论插入在第一次写还是第二次写的地方, 然后线性匹配, 如果失配, 有一次跳过的机会, 说明跳过的位置是插入的位置.
如果跳过后仍失配, 则这种情况不合法.
对于两种情况, 如果只有一种匹配成功, 则直接输出对应字符串.
如果都不成功, 则无解.
如果两个都匹配成功, 对两个答案跑匹配, 如果仍匹配, 直接输出答案, 否则答案不唯一.
```cpp
unsigned m, n, Cnt(0), Flg(0), A, B, C, D, t, Ans1(0), Ans2(0), Tmp(0);
char a[2000005];
int main() {
n = RD(), m = n >> 1;
if(!(n & 1)) {
printf("NOT POSSIBLE\n");
return 0;
}
fread(a + 1, 1, 2000002, stdin);
Flg = 0;
for (register unsigned i(1); i <= m; ++i) {
if(a[i] ^ a[i + Flg + m]) {
if(Flg) {
Ans1 = 0x3fffffff;
break;
}
if(a[i] == a[i + m + 1]) {
Ans1 = 1;
Flg = 1;
} else {
Ans1 = 0x3fffffff;
break;
}
}
}
Flg = 0;
for (register unsigned i(1); i <= m; ++i) {
if(a[i + Flg] ^ a[i + m + 1]) {
if(Flg) {
Ans2 = 0x3fffffff;
break;
}
if(a[i + 1] == a[i + m + 1]) {
Ans2 = 1;
Flg = 1;
} else {
Ans2 = 0x3fffffff;
break;
}
}
}
if((Ans1 > 0x3f3f3f3f) && (Ans2 > 0x3f3f3f3f)) {
printf("NOT POSSIBLE\n");
return 0;
}
if((Ans1 < 0x3f3f3f3f) && (Ans2 < 0x3f3f3f3f)) {
for (register unsigned i(1); i <= m; ++i) {
if(a[i] != a[i + m + 1]) {
printf("NOT UNIQUE\n");
return 0;
}
}
}
if(Ans1 < 0x3f3f3f3f) {
for (register unsigned i(1); i <= m; ++i) {
putchar(a[i]);
}
putchar('\n');
return 0;
}
for (register unsigned i(m + 2); i <= n; ++i) {
putchar(a[i]);
}
putchar('\n');
return Wild_Donkey;
}
```
### B
给一个环, 每次选择 $a_i$, 给左右两个相邻的数加上 $a_i$, 将 $a_i$ 变成 $-a_i$. 求最少操作次数, 使得所有数非负.
发现总和永远不变, 所以 $Sum < 0$ 时不可能有解.
而 $Sum = 0$ 也不会有解, 因为无论如何都不能给出状态 `0 0...0 0 0` 是如何变换来的.
所以有解当且仅当 $Sum \geq 1$ 的时候有解.
虽然赛时的数据结构优化贪心被卡成筛子 (暴力有 $28'$), 但是这篇代码仍然可圈可点, 因为它将人类的创造力和行为艺术发挥到了极致.
每次选择序列中最小的元素, 然后对它进行如此操作, 用线段树维护这个序列, $O(1)$ 取最小值, $O(\log n)$ 修改.
操作过程中统计操作次数, 直到最小值非负跳出程序.
接下来是这篇伟大的得了 $11'$ 的代码, 复杂度 $n \sum a_i \log n$.
```cpp
unsigned a[200005], m, n, Cnt(0), A, B, C, D, t, Sum(0), Ans(0), Tmp(0);
struct Node {
Node *LS, *RS;
unsigned Val, Pos, Min;
}N[400005], *CntN(N);
inline void Clr() {
n = RD(), Sum = 0, CntN = N, Cnt = 0;
}
void Udt(Node *x) {
if(x->LS->Val <= x->RS->Val) {
x->Val = x->LS->Val;
x->Pos = x->LS->Pos;
} else {
x->Val = x->RS->Val;
x->Pos = x->RS->Pos;
}
}
void Build(Node *x, unsigned L, unsigned R) {
if(L == R) {
x->LS = x->RS = NULL;
x->Min = a[L], x->Pos = L, x->Val = 0x3f3f3f3f;
return;
}
register unsigned Mid = ((L + R) >> 1);
Build(x->LS = ++CntN, L, Mid);
Build(x->RS = ++CntN, Mid + 1, R);
x->Min = min(x->LS->Min, x->RS->Min);
Udt(x);
}
void Chg(Node *x, unsigned L, unsigned R) {
if(L == R) {
x->Val = B, x->Pos = A;
return;
}
register unsigned Mid((L + R) >> 1);
if(A <= Mid) Chg(x->LS, L, Mid);
else Chg(x->RS, Mid + 1, R);
Udt(x);
}
void Chg2(Node *x, unsigned L, unsigned R) {
if(L == R) {
x->Min = a[A];
return;
}
register unsigned Mid((L + R) >> 1);
if(A <= Mid) Chg2(x->LS, L, Mid);
else Chg2(x->RS, Mid + 1, R);
x->Min = min(x->LS->Min, x->RS->Min);
}
void Add (unsigned x) {
A = x;
if(a[x] < 1000000) {
B = 2000000 - a[x];
if(x == 1) {
if(a[n] > 1000000) B -= min(1000000 - a[x], a[n] - 1000000);
} else {
if(a[x - 1] > 1000000) B -= min(1000000 - a[x], a[x - 1] - 1000000);
}
if(x == n) {
if(a[1] > 1000000) B -= min(1000000 - a[x], a[1] - 1000000);
} else {
if(a[x + 1] > 1000000) B -= min(1000000 - a[x], a[x + 1] - 1000000);
}
Chg(N, 1, n);
} else {
B = 0x3f3f3f3f;
Chg(N, 1, n);
}
}
int main() {
while (1){
Clr();
if(!n) break;
for (register unsigned i(1); i <= n; ++i) {
Sum += (a[i] = (RDsg() + 1000000));
}
if(Sum <= (n * 1000000)) {
printf("-1\n");
continue;
}
Build(N, 1, n);
if(N[0].Min >= 1000000) {
printf("0\n");
continue;
}
for (register unsigned i(1); i <= n; ++i) {
Add(i);
}
register unsigned Now;
while (N[0].Min < 1000000) {
++Cnt;
Now = N[0].Pos;
if(Now == 1) {
a[n] -= 1000000 - a[Now];
A = n;
Chg2(N, 1, n);
} else {
a[Now - 1] -= 1000000 - a[Now];
A = Now - 1;
Chg2(N, 1, n);
}
if(Now == n) {
a[1] -= 1000000 - a[Now];
A = 1;
Chg2(N, 1, n);
} else {
a[Now + 1] -= 1000000 - a[Now];
A = Now + 1;
Chg2(N, 1, n);
}
a[Now] = 2000000 - a[Now];
A = Now;
Chg2(N, 1, n);
Add(Now);
if(Now == 1) Add(n);
else Add(Now - 1);
if(Now == n) Add(1);
else Add(Now + 1);
}
printf("%u\n", Cnt);
}
return Wild_Donkey;
}
```
然后在好心的 JJK 的提醒下, 我的贪心太小心了, 这个题可以直接见到负数就修改, 于是可以多源 BFS.
队列里存储所有负数, 发现每次操作除了操作的数字, 不存在别的位置的数变大的情况, 所以队列一定是只能在队首弹出的, 而每次可能变成负数的也只有操作数字两侧的数字, 所以可以每次只判断两个数是否入队.
这份代码得了 $28'$, 堪称用更短的代码拿更多的分的典范.
复杂度貌似是 $O(n \sum a_i)$.
```cpp
int a[200005];
long long Sum(0);
unsigned Q[100000005], Tl, Hd, m, n, Cnt(0);
unsigned A, B, C, D, t, Ans(0), Tmp(0);
char InQue[200005];
inline void Clr() {
n = RD(), Sum = 0, Tl = Hd = 0, Cnt = 0;
}
int main() {
while (1) {
Clr();
if (!n) break;
for (unsigned i(1); i <= n; ++i) Sum += (a[i] = RDsg());
if (Sum <= 0) { printf("-1\n");continue; }
for (register unsigned i(1); i <= n; ++i) if (a[i] < 0) Q[++Tl] = i, InQue[i] = 1;
while (Tl ^ Hd) {
unsigned Now(Q[++Hd]);
++Cnt, InQue[Now] = 0;
if (Now == 1) { a[n] += a[Now]; if ((!InQue[n]) && (a[n] < 0)) Q[++Tl] = n, InQue[n] = 1; }
else { a[Now - 1] += a[Now]; if ((!InQue[Now - 1]) && (a[Now - 1] < 0)) Q[++Tl] = Now - 1, InQue[Now - 1] = 1; }
if (Now == n) { a[1] += a[Now]; if ((!InQue[1]) && (a[1] < 0)) Q[++Tl] = 1, InQue[1] = 1; }
else { a[Now + 1] += a[Now]; if ((!InQue[Now + 1]) && (a[Now + 1] < 0)) Q[++Tl] = Now + 1, InQue[Now + 1] = 1; }
a[Now] = -a[Now];
}
printf("%u\n", Cnt);
}
return Wild_Donkey;
}
```
因为要求所有元素非负, 所以最终序列前缀和 (包括第 $0$ 位) 一定是单调不降并且极差等于 $m$.
发现每次对 $i$ 执行操作是交换 $i$ 和 $i - 1$ 的前缀和. 但是当 $i = n$ 或 $i = 1$ 的时候并不是这样.
但是当断点右移的时候, 如果只看相对大小, 那么前缀和数组相当于取出第一个元素, 增加 $Sum$ 放到末尾后面, 这样就可以写出同时加上第一个元素的前缀和, 也可以取最后一个元素, 减去 $Sum$ 放到末尾.
于是又有一个贪心, 每次将前 $[1, n)$ 的序列排序 (我们可以直接交换这些元素, 从而实现排序), 然后统计入答案, 取第一个元素加 $Sum$ 放到末尾, 将第一个元素弹出.
显然可以通过平衡树维护逆序对从而将时间复杂度优化到 $O(\sum a_i \log n)$.
```cpp
int a[200005];
long long Sum[200005], A, B, m;
unsigned n;
unsigned C, D, t, Ans(0), Tmp(0);
char InQue[200005];
struct Node {
Node* LS, * RS;
long long ValL, ValR;
unsigned Size;
}N[400005], * Stack[400005], ** Top(Stack), * Root(N);
inline void Clr() {
n = RD(), Tmp = Ans = 0, Root = N, Top = Stack;
}
Node* Insert(Node* x) {
++x->Size;
if (x->Size == 2) {
Node* Now(*(Top--));
Now->ValL = Now->ValR = A, Now->Size = 1, Now->LS = Now->RS = NULL;
if (A > x->ValL) x->RS = Now, x->LS = *(Top--), x->LS->Size = 1, x->LS->LS = x->LS->RS = NULL, x->LS->ValL = x->LS->ValR = x->ValL;
else x->LS = Now, x->RS = *(Top--), x->RS->Size = 1, x->RS->LS = x->RS->RS = NULL, x->RS->ValL = x->RS->ValR = x->ValR;
x->ValL = x->LS->ValL, x->ValR = x->RS->ValR;
return x;
}
if ((x->LS) && (x->LS->ValR >= A)) x->LS = Insert(x->LS), x->ValL = x->LS->ValL;
else x->RS = Insert(x->RS), x->ValR = x->RS->ValR;
if (!(x->LS)) { *(++Top) = x; return x->RS; }
if (!(x->RS)) { *(++Top) = x; return x->LS; }
if (x->Size > 3) {
if ((x->LS->Size << 1) < x->RS->Size) {
Node* Now(x->RS);
x->RS = Now->RS, Now->RS = Now->LS, Now->LS = x->LS, x->LS = Now;
Now->ValL = Now->LS->ValL, Now->ValR = Now->RS->ValR;
Now->Size = Now->LS->Size + Now->RS->Size;
}
if ((x->RS->Size << 1) < x->LS->Size) {
Node* Now(x->LS);
x->LS = Now->LS, Now->LS = Now->RS, Now->RS = x->RS, x->RS = Now;
Now->ValL = Now->LS->ValL, Now->ValR = Now->RS->ValR;
Now->Size = Now->LS->Size + Now->RS->Size;
}
}
return x;
}
Node* Del(Node* x) {
--(x->Size);
if (!(x->Size)) { *(++Top) = x; return NULL; }
if ((x->LS) && (x->LS->ValR >= A)) { x->LS = Del(x->LS); }
else { x->RS = Del(x->RS); }
if (!(x->LS)) { *(++Top) = x; return x->RS; }
if (!(x->RS)) { *(++Top) = x; return x->LS; }
if (x->Size > 3) {
if ((x->LS->Size << 1) < x->RS->Size) {
Node* Now(x->RS);
x->RS = Now->RS, Now->RS = Now->LS, Now->LS = x->LS, x->LS = Now;
Now->ValL = Now->LS->ValL, Now->ValR = Now->RS->ValR;
Now->Size = Now->LS->Size + Now->RS->Size;
}
if ((x->RS->Size << 1) < x->LS->Size) {
Node* Now(x->LS);
x->LS = Now->LS, Now->LS = Now->RS, Now->RS = x->RS, x->RS = Now;
Now->ValL = Now->LS->ValL, Now->ValR = Now->RS->ValR;
Now->Size = Now->LS->Size + Now->RS->Size;
}
}
x->ValL = x->LS->ValL, x->ValR = x->RS->ValR;
return x;
}
unsigned Find(Node* x) {
if (x->Size == 1) return (unsigned)(x->ValL > A);
if ((x->RS) && (x->RS->ValL > A)) return ((x->LS) ? (Find(x->LS) + x->RS->Size) : x->RS->Size);
else return Find(x->RS);
}
int main() {
while (1) {
Clr();
if (!n) break;
for (unsigned i(1); i <= n; ++i) Sum[i] = Sum[i - 1] + (a[i] = RDsg());
for (unsigned i(1); i <= (n << 1); ++i) *(++Top) = N + i;
m = Sum[n], N->ValL = N->ValR = a[1], N->Size = 1, N->LS = N->RS = 0;
if (m <= 0) { printf("-1\n");continue; }
for (register unsigned i(2); i < n; ++i) A = Sum[i], Ans += Find(Root), Root = Insert(Root);
A = m, Tmp = Find(Root), Root = Insert(Root), B = Root->ValL;
while (Root->ValL + m < Root->ValR) {
Ans += Tmp;
A = B, Root = Del(Root);
A += m, Tmp = Find(Root), B = Root->ValL, Root = Insert(Root);
}
printf("%u\n", Ans + Tmp);
}
return Wild_Donkey;
}
```
但是很遗憾, 这份代码没有得分, 因为它是假的, 每次不一定需要取最小的, 也没有必要维护 $[1, n)$ 前缀和单增, 只需要取最靠左的 $\leq Max - m$ 的数字放到末尾, 然后答案加上这个数字到左端的交换数即可. 如果没有这样的数字, 则取最小值. 直到极差等于 $m$ 为止.
但是这样极难维护, 而且算法复杂度 $O(\sum a_i\log n)$ 和 $O(n^2 \log n)$ 同阶, 也不是对的.
单调不降还要求前缀和数组第 $1$ 位大于等于第 $0$ 位, 也就是前缀和数组不能出现负数.
所以我们可以在任意一个位置开始求前缀和, 取最小值出现的位置, 然后以该点为末尾, 该点之后的一个元素为起点, 求出新的前缀和数组, 则这个新的前缀和一定都大于等于 $0$.
证明也很简单, 因为 $Sum \leq 0$ 的情况已经输出 $-1$ 了, 所以 $Sum > 0$.
又因为前缀和相当于是一个折线, 而这个折线终点到起点的高度差就是 $Sum$, 起点可以认为是 $0$. 因为问题是一个环, 所以这个折线可以在自己的终点处再连接无数个自己, 相当于在环上循环求前缀和, 每个循环跑一圈, 前缀和增加 $Sum$.
那么就可以尝试找出这个折线的最低点, 以这里为起点, 那么再它后面的一个循环中, 一定不会有点低于它, 又因为这个最低点的值是 $0$, 所以折线上不存在负数.
发现只要我们愿意, 一定能在逆序对的数量次交换中得到有序的序列, 而这个交换次数是不可避免的, 所以不如一开始就将数组排序, 然后将逆序对数统计到答案中.
找到这个断开的位置, 重新求出前缀和, 求出逆序对数量, 然后记录进入答案. 这时就可以排序了.
接下来考虑减少极差.
我们取第一个元素 $a$, 增加 $Sum$ 放到末尾, 但是这样会产生 $x$ 逆序对, 将 $x$ 加入答案即可再次得到单调序列.
发现每次进行这个操作后, $a$ 到每个比它大超过 $Sum$ 的元素的差就会减少 $m$, 所以每对数字 $a < b$, 都会贡献 $\lfloor \frac{b - a - 1)}{Sum} \rfloor$ 的答案.
所以我们需要做的就是将前缀和 $S$ 数组排序, 然后统计两两差 $\lfloor \frac{(Dif_{i, j} - 1)}{Sum} \rfloor$ 的总和.
因为 $Sum$ 非常小, 所以我们可以考虑将 $S$ 对 $\mod Sum$ 进行分类, 对每个 $S_i$ 枚举前面的数对 $Sum$ 的模数, 统计答案.
本来是可以用离散化和树状数组干过去的, 但是我舍不得我的平衡树, 所以我就用平衡树求逆序对了.
```cpp
long long a[100005], b[100005];
long long Sum[100005], Min, m(0), Pos;
long long A, Pls[15], Ans;
long long n, Cnt, PCnt[15], Last;
struct Node {
Node* LS, * RS;
long long ValL, ValR;
unsigned Size;
}N[200005], * CntN(N), * Root(N);
inline void Clr() {
// printf("Done?\n");
if (m > 0) memset(Pls, 0, m << 3), memset(PCnt, 0, m << 3);
// printf("Half\n");
n = RD(), Ans = 0, Min = 0x3f3f3f3f3f3f3f3f, Root = CntN = N;
// printf("Done!\n");
}
void Print(Node* x) {
printf("Size %u [%lld, %lld] LS %u, RS %u\n", x->Size, x->ValL, x->ValR, x->LS - N, x->RS - N);
}
Node* Insert(Node* x) {
++x->Size;
if (x->Size == 2) {
Node* Now(++CntN);
Now->ValL = Now->ValR = A, Now->Size = 1, Now->LS = Now->RS = NULL;
if (A > x->ValL) x->RS = Now, x->LS = ++CntN, x->LS->Size = 1, x->LS->LS = x->LS->RS = NULL, x->LS->ValL = x->LS->ValR = x->ValL;
else x->LS = Now, x->RS = ++CntN, x->RS->Size = 1, x->RS->LS = x->RS->RS = NULL, x->RS->ValL = x->RS->ValR = x->ValR;
x->ValL = x->LS->ValL, x->ValR = x->RS->ValR;
return x;
}
if ((x->LS) && (x->LS->ValR >= A)) x->LS = Insert(x->LS), x->ValL = x->LS->ValL;
else x->RS = Insert(x->RS), x->ValR = x->RS->ValR;
if (!(x->LS)) { return x->RS; }
if (!(x->RS)) { return x->LS; }
if (x->Size > 3) {
if ((x->LS->Size << 1) < x->RS->Size) {
Node* Now(x->RS);
x->RS = Now->RS, Now->RS = Now->LS, Now->LS = x->LS, x->LS = Now;
Now->ValL = Now->LS->ValL, Now->ValR = Now->RS->ValR;
Now->Size = Now->LS->Size + Now->RS->Size;
}
if ((x->RS->Size << 1) < x->LS->Size) {
Node* Now(x->LS);
x->LS = Now->LS, Now->LS = Now->RS, Now->RS = x->RS, x->RS = Now;
Now->ValL = Now->LS->ValL, Now->ValR = Now->RS->ValR;
Now->Size = Now->LS->Size + Now->RS->Size;
}
}
return x;
}
unsigned Find(Node* x) {
if (x->Size == 1) return (unsigned)(x->ValL > A);
if ((x->RS) && (x->RS->ValL > A)) return ((x->LS) ? (Find(x->LS) + x->RS->Size) : x->RS->Size);
else return Find(x->RS);
}
int main() {
for (;;) {
Clr();
if (!n) break;
for (unsigned i(1); i <= n; ++i) { Sum[i] = Sum[i - 1] + (a[i] = RDsg()); if (Sum[i] < Min) Min = Sum[i], Pos = i; }
if ((m = Sum[n]) <= 0) { printf("-1\n");continue; }
for (unsigned i(1); i <= n; ++i) { b[i] = a[(Pos + i > n) ? (Pos + i - n) : (Pos + i)]; }
for (unsigned i(1); i <= n; ++i) Sum[i] = Sum[i - 1] + b[i];
Root->ValL = Root->ValR = Sum[1], Root->Size = 1, Root->LS = Root->RS = NULL;
for (unsigned i(2); i <= n; ++i) A = Sum[i], Ans += Find(Root), Root = Insert(Root);
sort(Sum + 1, Sum + n + 1, greater<long long>()), Sum[n + 1] = 0x3f3f3f3f3f3f3f3f, Last = 1, Cnt = 1;
for (unsigned i(1); i <= n; ++i, ++Cnt) {
if (Sum[i] ^ Sum[i + 1]) {
Pos = Sum[i] % m, A = Sum[i] / m;
for (unsigned j(0); j < m; ++j) Ans += (Pls[j] - PCnt[j] * (A + ((Pos < j) ? 0 : 1))) * Cnt;
Pls[Pos] += Cnt * A, PCnt[Pos] += Cnt, Cnt = 0;
}
}
printf("%lld\n", Ans);
}
return Wild_Donkey;
}
```
但是仍然遗憾, 我的程序得了 $100'$, 但是是过题的三个人的最劣解, 而且严重低于平均, 在别人 $1100+ms$ 的时候, 我跑了 $1300+ms$.
然后为了抢最优解, 我选择了树状数组, 然后以 $715ms$ 过掉了此题, 堪称用更短的代码跑更快的常数的典范. (其实去掉缺省源, 我就是最短解)
```cpp
int a[100005], m;
unsigned BIT[100005], InvSum[100005];
long long Sum[100005], TmpSum[100005], Min;
unsigned long long A, Pls[15], Ans;
unsigned n, Cnt, PCnt[15], Pos;
inline void Clr() {
memset(BIT, 0, (n + 1) << 2);
if ((m > 0) && (m <= 10)) memset(Pls, 0, m << 3), memset(PCnt, 0, m << 2);
n = RD(), Ans = 0, Min = 0x3f3f3f3f3f3f3f3f;
}
int main() {
for (Clr();n; Clr()) {
for (unsigned i(1); i <= n; ++i) { Sum[i] = Sum[i - 1] + (a[i] = RDsg()); if (Sum[i] < Min) Min = Sum[i], Pos = i; }
if (Sum[n] <= 0) { printf("-1\n");continue; }
for (unsigned i(1); i <= n; ++i) TmpSum[i] = Sum[i] = Sum[i - 1] + a[(Pos + i > n) ? (Pos + i - n) : (Pos + i)];
sort(Sum + 1, Sum + n + 1, greater<long long>());
for (unsigned i(1); i <= n; ++i) InvSum[i] = lower_bound(Sum + 1, Sum + n + 1, TmpSum[i], greater<long long>()) - Sum;
for (unsigned i(1); i <= n; ++i) {
for (unsigned j(InvSum[i] - 1); j; j -= Lowbit(j)) Ans += BIT[j];
for (unsigned j(InvSum[i]); j <= n; j += Lowbit(j)) ++BIT[j];
}
Sum[n + 1] = 0x3f3f3f3f3f3f3f3f, Cnt = 1, m = TmpSum[n];
for (unsigned i(1); i <= n; ++i, ++Cnt) {
if (Sum[i] ^ Sum[i + 1]) {
Pos = Sum[i] % m, A = Sum[i] / m;
for (unsigned j(0); j < m; ++j) Ans += (Pls[j] - PCnt[j] * (A + ((Pos < j) ? 0 : 1))) * Cnt;
Pls[Pos] += Cnt * A, PCnt[Pos] += Cnt, Cnt = 0;
}
}
printf("%llu\n", Ans);
}
return Wild_Donkey;
}
```
我愿称此题为最难, 难在它的题解~~简洁~~晦涩难懂, 难在它没有标程 (然后在仅仅过掉的三个人中, 有一个是贺的另一个人的, 直接给我笑吐了, 贺别人的跑得还没人家快). 所以我前后总共写了 $5$ 篇代码, 占据了非常大的篇幅.
### C
稍微转化一波发现是求所有本质不同的子串出现次数的平方和.
发现 $n = 1$ 的情况很好做, 只需要建 SAM, 定义 $Cnt$ 为后缀树子树内的前缀节点数量, 然后求每个点 $(Len - Fail_{Len})Cnt^2$ 之和即可.
当 $n > 1$ 时, 可以在线构造 GSAM, 然后对每个询问遍历后缀树, 同样能求出答案, 但是时间复杂度是 $n\sum L$, 只能得 $58'$.
接下来考虑优化.
因为我们只要知道哪些点的 $Cnt$ 或 $Fail$ 改变了即可 (因为一个点的 $L$ 一旦确定永不改变, 所以不用讨论).
对于 $Fail$ 来说, 只有分裂的时候会修改, 所以需要讨论的点数和 $L$ 有关. 而 $Cnt$ 则是一个点改变引起整条链的 $Cnt$ 值改变, 所以考虑树剖, 但是树剖不能在线维护, 所以现在有两条路: 离线树剖和 LCT 在线维护.
因为本题没有强制在线, 所以考虑树剖. 需要实现维护 $(Len - Fail_{Len})Cnt^2$, 则需要维护 $Dif = Len - Fail_{Len}$ 的单点修改, $Cnt$ 的区间加, $Cnt^2$ 的区间加, 记录每个字符串插入的时候的操作序列, 离线回答即可.
这个题的思路非常套路, 但是它的难点在代码.
```cpp
unsigned m, n, A, B, C, D, t, Ans(0), Tmp(0), NofS;
unsigned CntE(0), CntD(0), Pos[100005];
char a[300005], NowC;
struct Edit {
unsigned Nid, Opt;
}E[1200005];
struct Seg {
Seg* LS, * RS;
unsigned long long DiCnt2, DiCnt, Di;
unsigned TagofPCnt;
}S[1200005], * CntS(S);
void PsDw(Seg* x) {
if (x->TagofPCnt) {
unsigned long long Sq(x->TagofPCnt * x->TagofPCnt);
(x->LS->TagofPCnt) += x->TagofPCnt, (x->RS->TagofPCnt) += x->TagofPCnt;
x->LS->DiCnt2 += ((x->LS->DiCnt << 1) * x->TagofPCnt) + Sq * x->LS->Di;
x->RS->DiCnt2 += ((x->RS->DiCnt << 1) * x->TagofPCnt) + Sq * x->RS->Di;
x->LS->DiCnt += x->LS->Di * x->TagofPCnt;
x->RS->DiCnt += x->RS->Di * x->TagofPCnt;
x->TagofPCnt = 0;
}
}
void PsUp(Seg* x) {
x->DiCnt2 = x->LS->DiCnt2 + x->RS->DiCnt2;
x->DiCnt = x->LS->DiCnt + x->RS->DiCnt;
x->Di = x->LS->Di + x->RS->Di;
}
void Build(Seg* x, unsigned L, unsigned R) {
if (L == R) return;
unsigned Mid((L + R) >> 1);
Build(x->LS = ++CntS, L, Mid), Build(x->RS = ++CntS, Mid + 1, R);
}
void ChgCnt(Seg* x, unsigned L, unsigned R) {
if ((A <= L) && (R <= B)) { x->DiCnt2 += (x->DiCnt << 1) + x->Di, x->DiCnt += x->Di, ++(x->TagofPCnt);return; }
unsigned Mid((L + R) >> 1);
PsDw(x);
if (A <= Mid) ChgCnt(x->LS, L, Mid);
if (Mid < B) ChgCnt(x->RS, Mid + 1, R);
PsUp(x);
}
void ChgDif(Seg* x, unsigned L, unsigned R) {
if (L == R) { x->DiCnt = x->TagofPCnt * B, x->DiCnt2 = x->TagofPCnt * x->DiCnt, x->Di = B;return; }
unsigned Mid((L + R) >> 1);
PsDw(x);
if (A <= Mid) ChgDif(x->LS, L, Mid);
else ChgDif(x->RS, Mid + 1, R);
PsUp(x);
}
struct Node {
Node* To[26], * Fail, * Son, * Bro, * Heavy, * Top;
unsigned Len, DFSr, Size;
}N[600005], * CntN(N), * Last(N);
void Add() {
register Node* Back(Last);
if (Back->To[NowC]) {
if (Back->To[NowC]->Len == Back->Len + 1) Last = Back->To[NowC];
else {
Node* Bfr(Back->To[NowC]);
*(Last = ++CntN) = *Bfr, Last->Len = Back->Len + 1, Bfr->Fail = Last;
E[++CntE].Nid = Bfr - N, E[CntE].Opt = Bfr->Len - Last->Len;
E[++CntE].Nid = Last - N, E[CntE].Opt = Last->Len - Last->Fail->Len;
while (Back && (Back->To[NowC] == Bfr)) Back->To[NowC] = Last, Back = Back->Fail;
}
return;
}
Last = ++CntN, Last->Len = Back->Len + 1;
while (Back && (!(Back->To[NowC]))) Back->To[NowC] = Last, Back = Back->Fail;
if (Back) {
if ((Back->To[NowC]->Len) ^ (Back->Len + 1)) {
Node* Bfr(Back->To[NowC]);
*(++CntN) = *Bfr, CntN->Len = Back->Len + 1, Bfr->Fail = Last->Fail = CntN;
E[++CntE].Nid = Bfr - N, E[CntE].Opt = Bfr->Len - CntN->Len;
E[++CntE].Nid = Last - N, E[CntE].Opt = Last->Len - CntN->Len;
E[++CntE].Nid = CntN - N, E[CntE].Opt = CntN->Len - CntN->Fail->Len;
while (Back && (Back->To[NowC] == Bfr)) Back->To[NowC] = CntN, Back = Back->Fail;
}
else Last->Fail = Back->To[NowC], E[++CntE].Nid = Last - N, E[CntE].Opt = Last->Len - Last->Fail->Len;
}
else Last->Fail = N, E[++CntE].Nid = Last - N, E[CntE].Opt = Last->Len;
}
void ChgAp(Node* x) {
while (x) A = x->Top->DFSr, B = x->DFSr, ChgCnt(S, 1, NofS), x = x->Top->Fail;
}
void PreDFS(Node* x) {
Node* Now(x->Son);
unsigned Mx(0);
x->Size = 1;
while (Now) {
PreDFS(Now);
x->Size += Now->Size;
if (Now->Size > Mx) x->Heavy = Now, Mx = Now->Size;
Now = Now->Bro;
}
return;
}
void DFS(Node* x) {
x->DFSr = ++CntD;
if (!(x->Heavy)) return;
Node* Now(x->Son);
x->Heavy->Top = x->Top, DFS(x->Heavy);
while (Now) {
if (Now != x->Heavy) { Now->Top = Now, DFS(Now); }
Now = Now->Bro;
}
return;
}
int main() {
n = RD();
for (unsigned i(1), j(1); i <= n; ++i, j = 1, Last = N) {
scanf("%s", a + 1);
while (a[j] < 'a') ++j;
while (a[j] >= 'a') NowC = a[j] -= 'a', Add(), a[j++] = 0, E[++CntE].Nid = Last - N, E[CntE].Opt = 0;
Pos[i] = CntE;
}
for (Node* i(N + 1); i <= CntN; ++i) i->Bro = i->Fail->Son, i->Fail->Son = i;
N->Top = N, PreDFS(N), DFS(N), NofS = CntN - N + 1, Build(S, 1, NofS);
for (unsigned i(1), j(1); i <= n; ++i) {
for (;j <= Pos[i]; ++j) {
if (E[j].Opt) A = N[E[j].Nid].DFSr, B = E[j].Opt, ChgDif(S, 1, NofS);
else ChgAp(N + E[j].Nid);
}
printf("%llu\n", S->DiCnt2);
}
return Wild_Donkey;
}
```
很遗憾拿了最劣解, 但是指针 SAM (尤指字符集大于等于 $26$ 的 SAM) 必然是最劣解吧...
但是非常羞耻, 我一个弱智树剖写了一晚上.
### D
貌似是第二简单的.
给一个 0/1 串 $S$, 求随机添加字符, 得到后缀 $S$ 停止, 求长度期望.
先分析边界, 如果 $S$ 只有一个 `1`, 则期望是 $2$, 这个值是 $\frac 12 + \frac 24 + \frac 38 + \frac 4{16} + ...$ 得到的, 转化为递归式 $x = 1 + 0 + \frac 12x$ 即可解出, 其意义是这一位无论选什么, 都会贡献 $1$ 的期望, 其中 $\frac 12$ 的几率选 $1$, 长度是 $0$, 剩下的 $\frac 12$, 仍然是这个问题的递归, 所以期望就是自己本身.
设计状态 $f_i$, 表示 $S$ 的 $(i, n]$ 作为后缀的期望长度, 或者说, $f_i$ 表示已经匹配了 $S$ 的前缀 $[1, i]$, 仍需要匹配多少能得到 $S$ 的期望值. 容易知道边界的 $f_n = 0$.
设 $Pos_i$ 为匹配 $i$ 位后失配, 当前字符串的最长的和 $S$ 的前缀匹配的后缀的长度, 这个可以在求 KMP 的 $Nxt$ 数组的同时求出, 这时可以写出转移方程:
$$
f_i = 1 + \frac 12 f_{i + 1} + \frac 12 f_{Pos_i}
$$
因为, $i + 1 > i$, $Pos_i \leq i$ 所以不方便直接 DP, 所以选择设 $Dif_i$ 为 $f_{i - 1} - f_i$, 通过递推式, 解出 $Dif_{i + 1}$.
$$
Dif_{i + 1} = f_{i} - f_{i + 1} = 2 + \sum_{j = Pos_i + 1}^{i} Dif_j
$$
发现这个时候, $j \leq i$, 所以这个时候是可以正序递推的, 加一个前缀和就可以优化到 $O(n)$.
所求答案是 $f_0$, 所以只需要将 $Dif$ 求和就是答案.
不过还有生成函数的做法, 它非常好写, 而且非常容易扩展到一般的字符串, 而不仅仅是 $0/1$ 串.
设 $f(i)$ 是放了 $i$ 个数, 刚好第一次得到 $S$ 的概率, $g(i)$ 是放了 $i$ 个数, 还没有得到 $S$ 的概率.
设 $F(x)$ 和 $G(x)$ 是 $f(x)$ 和 $g(x)$ 的生成函数.
因为 $g(i)$ 表示的情况中, 再选第 $i + 1$ 个字符, 有且仅有两种情况, 即第 $i + 1$ 位得到 $S$ 了, 和第 $i + 1$ 位没得到 $S$, 所以有式子:
$$
g(i) = f(i + 1) + g(i + 1)
$$
对应到生成函数里就是:
$$
1 + xG(x) = F(x) + G(x)
$$
对于 $g(i)$, 也就是还没有出现 $S$ 的状态, 如果这时在后面连续放一个 $S$ 串, 一定能得到一个 $S$, 并且结束. 但是有可能会提前得到 $S$ 而结束.
对于 $g(i)$ 状态后面恰好放一整个 $S$ 的情况, 它要求后面每一次加字符都恰好得到我们想要的, 所以这种理想状态的概率为 $\frac{g(i)}{2^n}$.
这时定义一个布尔数组 $IsBd$, 其中 $IsBd_i$ 表示 $S$ 的前缀 $[1, i]$ 是否和后缀 $[n - i + 1, n]$ 匹配.
因为 $g(i)$ 涵盖了所有选择 $i$ 个字符仍未得到 $S$ 的情况, 所以 $f(i + j) [IsBd_j]$ 所表示的所有情况都应该被 $\frac{g(i)}{2^n}$ 所包含. 但是显然在 $i + j$ 就已经得到 $S$ 的情况不管本来打算在 $(i + j, i + n]$ 放什么都无所谓了, 但是我们之前规定 $i$ 后面要放整个 $S$, 所以相当于得到 $S$ 后仍然强制放 $n - j$ 个我们想要的字符, 这就需要在原本的 $f_{i + j}$ 上除以 $2^{n - j}$ 得到它在 $\frac {g(i)}{2^n}$.
$$
\frac{g(i)}{2^n} = \sum_{IsBd_j} \frac{f(i + j)}{2^{n - j}}
$$
仍然对应到生成函数:
$$
\frac{G(x)}{2^n} = \sum_{IsBd_j} \frac{F(x)}{2^{n - j}x^{j}}
$$
化简得到:
$$
G(x) = \sum_{IsBd_j} \frac{F(x)2^j}{x^{j}}
$$
因为本题是求期望, 所以考虑概率和期望的关系, 发现概率生成函数 $F(x)$ 的导数 $F'(x)$ 乘一个 $x$ 就是期望的生成函数, 因为:
$$
F(x) = \sum_{i = 0}^{\infty} a_ix^i\\
F'(x) = \sum_{i = 0}^{\infty} ia_ix^{i - 1}\\
xF'(x) = \sum_{i = 0}^{\infty} ia_ix^{i}
$$
这时, 如果将 $x$ 取 $1$, 发现 $F'(1) = \sum ia_i$ 即为所求答案.
将 $i$ 代入之前的式子:
$$
\begin {aligned}
1 + xG(x) &= F(x) + G(x)\\
1 + G(1) &= F(1) + G(1)\\
G'(1) + G(1) &= F'(1) + G'(1)\\
G(1) &= F'(1)\\
\end {aligned}
$$
发现答案转化为 $G(1)$, 然后回到刚刚的式子:
$$
\begin {aligned}
G(x) &= \sum_{IsBd_j} \frac{F(x)2^j}{x^{j}}\\
G(1) &= \sum_{IsBd_j} F(1)2^j
\end {aligned}
$$
最后只要求出 $F(1)$ 就可以了.
$$
\begin {aligned}
1 + G(1) &= F(1) + G(1)\\
1 &= F(1)\\
\end {aligned}
$$
然后得出结论:
$$
Ans = F'(1) = G(1) = G(1) = \sum_{IsBd_j} F(1)2^j = \sum_{IsBd_j} 2^j
$$
所以只要从 $i = n$ 不断跳 $Nxt_i$, 并且对 $2^i$ 求和即可. 这个做法可以推广到任意字符集大小的问题.
```cpp
unsigned Mod(1000000007);
unsigned Nxt[1000005], f[1000005], Bin[1000005], m, n(0), Cnt(0), A, B, C, D, t, Ans(0), Tmp(0);
char a[1000005];
int main() {
fread(a + 1, 1, 1000002, stdin);
Nxt[1] = 0, Bin[0] = 1;
for (register unsigned i(2), j(1); a[i] >= '0'; ++i) {
n = i;
j = i - 1;
while (j && (a[Nxt[j] + 1] ^ a[i])) {
j = Nxt[j];
}
if(!j) {Nxt[i] = 0; continue;}
Nxt[i] = Nxt[j] + 1;
}
f[n] = 0;
for (register unsigned i(1); i <= n; ++i) {
Bin[i] = (Bin[i - 1] << 1);
if(Bin[i] >= Mod) Bin[i] -= Mod;
}
register unsigned i(n);
while (i) {
Ans += Bin[i];
if(Ans >= Mod) Ans -= Mod;
i = Nxt[i];
}
printf("%u\n", Ans);
return Wild_Donkey;
}
```
所以接下来是本题的加强版;
### [CTSC2006](https://www.luogu.com.cn/problem/P4548)
给一个字符集大小 $w$, 给 $t$ 个序列 $S_i$, 表示 $t$ 个询问.
询问每次随机选择字符集中一个字符加在末尾, 得到 $S_i$ 就退出, 选择的字符连成的串长期望的后 $4$ 位.
发现每个询问的答案, 可以直接由上一题推广而来:
$$
Ans = F'(1) = G(1) = G(1) = \sum_{IsBd_j} F(1)w^j = \sum_{IsBd_j} w^j
$$
```cpp
unsigned a[100005], Nxt[100005], Pow[100005], m, n, w;
unsigned Cnt(0), A, B, C, D, t, Ans(0), Tmp(0);
inline void Clr() {
n = RD(), Ans = 0;
}
signed main() {
w = RD() % 10000, t = RD(), Pow[0] = 1;
for (unsigned i(1); i <= 100000; ++i) Pow[i] = Pow[i - 1] * w % 10000;
for (unsigned T(1); T <= t; ++T) {
Clr();
for (unsigned i(1); i <= n; ++i) a[i] = RD();
for (unsigned i(1), j(0); i <= n; ++i, j = i - 1) {
while (j && (a[i] ^ a[Nxt[j] + 1])) j = Nxt[j];
Nxt[i] = j ? (Nxt[j] + 1) : 0;
}
while (n) { Ans += Pow[n]; if (Ans >= 10000) Ans -= 10000; n = Nxt[n]; }
printf("%04u\n", Ans);
}
system("pause");
return Wild_Donkey;
}
```
## Day14: [ACtion Movie](https://mbit.mbhs.edu/archive/2021s/standard.pdf)
[Sol](https://mbit.mbhs.edu/archive/2021s/standard_editorial.pdf)
### A
大模拟, 有 $N$ 个苹果, $M$ 个骨头, 需要 $A$ 个苹果, $B$ 个骨头.
需要 $X$ 时间生产一个苹果, $Y$ 时间生产一个骨头.
可以用 $C$ 个苹果换 $D$ 个骨头, 可以用 $D$ 个骨头换 $C$ 个苹果.
求得到至少 $A$ 苹果, $B$ 骨头的最短时间.
$A, B, C, D \leq 1000
首先有个性质: 只要 N \geq A + C 或 M \geq B + D, 一定把多出来的苹果或骨头换成另一种.
然后是 N + C \leq A 或 M + D \leq B, 这时如果生产 C 个苹果比 D 个骨头时间短, 则生产 C 个苹果换成 D 个骨头代替直接生产骨头, 反之亦然.
对于整段之后的零头, 仍然要讨论, 比如现在 N + C > A, 但是 A - N 个苹果生产仍然不如生产 D 个骨头, 那就选择 D 个骨头换出 C 个苹果.
还要讨论一开始形如 A < N < A + C 的情况, 因为有时候, 拿 C 个苹果换到 D 个骨头, 然后生产 C + A - N 个苹果的方案更优.
unsigned a[10005], N, M, X, Y, m, n, Cnt(0), A, B, C, D, t, TmpB, TmpA, Ans(0), Tmp1(0), Tmp2(0), Tmp(0);
char b[10005];
int main() {
N = RD(), M = RD(), X = RD(), Y = RD(), A = RD(), B = RD(), C = RD(), D = RD();
if((N >= A) && (M >= B)) {
printf("0\n");
return 0;
}
if(N >= A) {
M += ((N - A) / C) * D;
if(M >= B) {
printf("0\n");
return 0;
}
Tmp = (N - A + C - 1) / C;
TmpB = M + Tmp * D;
if(TmpB >= B) Tmp1 = X * (A + Tmp * C - N);
else Tmp1 = X * (A + Tmp * C - N) + min(((B - TmpB + D - 1) / D) * C * X, Y * (B - TmpB));
Tmp2 = min(((B - M + D - 1) / D) * C * X, Y * (B - M));
Ans += min(Tmp1, Tmp2);
printf("%u\n", Ans);
return 0;
}
if(M >= B) {
N += ((M - B) / D) * C;
if(N >= A) {
printf("0\n");
return 0;
}
Tmp = (M - B + D - 1) / D;
TmpA = N + Tmp * C;
if(TmpA >= A) Tmp1 = Y * (B + Tmp * D - M);
else Tmp1 = Y * (B + Tmp * D - M) + min(((A - TmpA + C - 1) / C) * D * Y, X * (A - TmpA));
Tmp2 = min(((A - N + C - 1) / C) * D * Y, X * (A - N));
Ans += min(Tmp1, Tmp2);
printf("%u\n", Ans);
return 0;
}
TmpA = ((A - N) / C) * D * Y + X * (A - (((A - N) / C) * C) - N);
TmpB = ((B - M) / D) * C * X + Y * (B - (((B - M) / D) * D) - M);
Ans += min(min(((A - N + C - 1) / C) * D * Y, X * (A - N)), TmpA);
Ans += min(min(((B - M + D - 1) / D) * C * X, Y * (B - M)), TmpB);
printf("%u\n", Ans);
return Wild_Donkey;
}
打表题, 打表发现, 无论 n 是多少, 最后前缀和中, [0, 2^{\lfloor \log n \rfloor + 1}) 每种数字出现次数相同.
所以对于 m, 求 \frac{2^n(m + 1)}{2^{\lfloor \log n \rfloor + 1}} = 2^{n - \lfloor \log n \rfloor - 1}(m + 1) 即可, 快速幂解决.
const unsigned long long MOD(1000000007);
unsigned long long m, n, Cnt(0), N, Ans(0), Tmp(0), a[1005];
unsigned long long Power(unsigned long long y) {
register unsigned long long Tmpx(2), Final(1);
while (y) {
if(y & 1) {
Final = Final * Tmpx % MOD;
}
Tmpx = Tmpx * Tmpx % MOD;
y >>= 1;
}
return Final;
}
int main() {
Tmp = n = RD(), m = RD();
while (Tmp) {
++Cnt;
Tmp >>= 1;
}
N = ((unsigned long long)1 << Cnt) - 1;
m = min(N, m);
printf("%llu\n", ((1 + m) % MOD) * Power(n - Cnt) % MOD);
return Wild_Donkey;
}