题解:P9043 [PA2021] Zbalansowane słowa

· · 题解

# Solution ## Part I 我们观察一个子串的合法条件,记 $S$ 为子串 $[l,r]$ 中字符组成的集合,$e_i$ 为 $i$ 的出现次数,从 $S$ 中任选一个数 $x$,可以得到条件为: $$\forall i\in S,e_i-e_x=0$$ 我们记 $s_{i,j}$ 是前 $i$ 个位置中 $j$ 的出现次数,则上述条件变为: $$\forall i\in S,s_{r,i}-s_{l-1,i}-s_{r,x}+s_{l-1,x}=0$$ $$\forall i\in S,s_{r,i}-s_{r,x}=s_{l-1,i}-s_{l-1,x}$$ 也就是说,我们固定 $S,x$,记 $d_{i,j}=s_{i,j}-s_{i,x}$,子串 $[l,r]$ 的合法条件就是: $$ \begin{cases} \forall i\in S,d_{r,i}=d_{l-1,i},\\ \forall i\notin S,s_{r,i}=s_{l-1,i} \end{cases} $$ ## Part II 我们要枚举的是 $S$,当然不能大力 $2^{\lvert\Sigma\rvert}$ 枚举。我们有结论: - 固定左端点 $l$,所有右端点 $r$ 产生 $S$ 的种类只有 $\lvert\Sigma\rvert$ 种。固定右端点同理。 我们考虑向右拓展,一种字符之后被加入一次。我们记 $L_i$ 是右端点为 $i$ 向左拓展过程中所有 $S$ 的集合,$R_i$ 是左端点为 $i$ 向右拓展过程中所有 $S$ 的集合。 我们同时枚举端点与集合。枚举左端点与集合 $(l,S)$($S\in R_l$)、右端点与集合 $(r,T)$($T\in L_r$)。为了方便,我们将 $x$ 取集合中最小值,即 $x=\min S,x'=\min T$。将 $d,s$ 统一,记 $p_{i,j}=s_{i,j}-\begin{cases}s_{i,x}&j\in S\\0&j\notin S\end{cases}$,$q_{i,j}=s_{i,j}-\begin{cases}s_{i,x'}&j\in T\\0&j\notin T\end{cases}$。可以得出 $[l,r]$ 合法的条件是: $$ \begin{cases} l\leq r,\\ S=T,\\ p_{l-1}=q_r \end{cases} $$ 大力枚举是 $\mathcal{O}(n^2\cdot\lvert\Sigma\rvert^2)$ 的,我们发现三个条件除了第一个都是相等,自然分别枚举 $(l,S),(r,T)$ 放进哈希表计数。 考虑到 $l\leq r$,我们枚举 $i=0,1,2,\cdots,n$,先统计以 $i$ 为右端点的答案,即枚举 $S\in L_i$,算出 $p$,答案加上哈希表里 $(S,p)$ 的个数;再枚举 $T\in R_{i-1}$,算出 $q$,将 $(T,q)$ 放入哈希表。 枚举的时候每次往 $S,T$ 里加入一个字符,所以 $p,q$ 与哈希值也是可以维护的。时间复杂度 $\mathcal{O}(n\cdot\lvert\Sigma\rvert)$。 细节见代码,有注释。 # Code ```cpp #include <bits/stdc++.h> #define REP(i, l, r) for (int i = (l); i <= (r); ++ i) #define DEP(i, r, l) for (int i = (r); i >= (l); -- i) #define fi first #define se second #define pb emplace_back #define mems(x, v) memset((x), (v), sizeof(x)) using namespace std; namespace Milkcat { typedef long long LL; typedef unsigned long long ULL; typedef pair<LL, LL> pii; const int N = 1e6 + 5, B = 1e7 + 7; LL n, m, rs, a[N], ct[N][26]; string st; ULL pw[N]; vector<int> L[N], R[N]; unordered_map<ULL, int> S; void ins(vector<int>& s, int x) { int t = -1; for (int i = 0; i < s.size(); i ++) if (s[i] == x) t = i; if (t != -1) s.erase(s.begin() + t); s.insert(s.begin(), x); } // s 中没有 x 则将 x 加到最前,有则将里面 x 的位置放到最前。 int main() { cin >> st, n = st.size(); REP(i, 1, n) { a[i] = st[i - 1] - 'a', m = max(m, a[i] + 1); REP(j, 0, 25) ct[i][j] = ct[i - 1][j] + (a[i] == j); // ct 是题解中的 s } REP(i, 1, n) L[i] = L[i - 1], ins(L[i], a[i]); DEP(i, n, 1) R[i] = R[i + 1], ins(R[i], a[i]); // 处理出 L,R pw[0] = 1; REP(i, 1, n + m) pw[i] = pw[i - 1] * B; REP(i, 0, n) { if (i > 0) { ULL v = 0, w = 0, s = 0, c = 1e9; // v 是 p 的哈希值,w 是 S 的哈希值,s 辅助求哈希值,c 是 x(最小值) DEP(j, m - 1, 0) v = v * B + ct[i][j]; for (int x : L[i]) { if (x < c) v += s * (c < m ? ct[i][c] - ct[i][x] : 0), c = x; // 最小值改变,要改变之前 -ct[i][c] 的部分(即 s) v -= ct[i][c] * pw[x], s += pw[x], w |= 1 << x; // 加入一个数 rs += S[v + w * pw[m]]; // 统计答案 } } ULL v = 0, w = 0, s = 0, c = 1e9; DEP(j, m - 1, 0) v = v * B + ct[i][j]; for (int x : R[i + 1]) { if (x < c) v += s * (c < m ? ct[i][c] - ct[i][x] : 0), c = x; v -= ct[i][c] * pw[x], s += pw[x], w |= 1 << x; // 和前面一模一样 S[v + w * pw[m]] ++; } } cout << rs << '\n'; return 0; } } int main() { // freopen("c.in", "r", stdin); // freopen("c.out", "w", stdout); ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); int T = 1; while (T --) Milkcat::main(); return 0; } ```