子串函数和:求字符串的所有子串出现次数n代入n(n+1)/2之和。
4.7华丽地处理字符串
后缀数组
题目有些绕,直白地讲就是枚举所有不同的子串,求出现次数,代入该表达式求和。枚举子串的通用做法是,高度数组连续一段不小于h的区间长度代表长h的子串的出现次数。将高度数组视作x轴上宽度为1,高度为height[i]的矩形序列的话,出现次数就是求高度大于h的最宽矩形。
这种问题的求法也有定式,就是类似书上栈的那节用单调栈的做法。定义一个公共前缀长度height的非严格递增栈。
每次插入height[i]时弹出的那些元素end与i共同构成“最宽矩形”的始末区间,所以矩形宽度为i-end。这些矩形的高度就对应着一个子串。
进一步,若子串A包含子串B,则说明有A.length-B.length个长B.length的子串出现次数都是i-end。如果某后缀的长度比当前height大,后缀的height也比当前height大,该公共前缀就包含当前公共前缀,就会发生这种情况了。
子串个数乘以f(子串出现次数),累加到答案上即可。
#include <iostream> #include <vector> #include <cstdio> #include <cstring> #include <utility> #include <algorithm> using namespace std; const int MAX_N = 100100; struct SuffixArray { int sa[MAX_N], height[MAX_N]; void build(const string &s) { build(s.c_str(), s.size()); } void build(const char *t, int n) { // suffix array static int g[MAX_N], b[MAX_N]; for (int i = 0; i < n + 1; ++i) { sa[i] = i, g[i] = t[i]; } b[0] = b[n] = 0; sort(sa, sa + n + 1, SAComp(0, g)); for (int h = 1; b[n] != n; h *= 2) { SAComp comp(h, g); sort(sa, sa + n + 1, comp); for (int i = 0; i < n; ++i) { b[i + 1] = b[i] + comp(sa[i], sa[i + 1]); } for (int i = 0; i < n + 1; ++i) { g[sa[i]] = b[i]; } } // longest common prefix (you can omit here) for (int i = 0; i < n + 1; ++i) { b[sa[i]] = i; } int h = 0; for (int i = 0; i < n; ++i) { const int j = sa[b[i] - 1]; while (j + h < n && i + h < n && t[j + h] == t[i + h]) h++; height[b[i]] = h; if (h) h--; } height[0] = -1; } struct SAComp { const int h, *g; SAComp(int h, int *g) : h(h), g(g) {} bool operator()(int l, int r) { return g[l] != g[r] ? g[l] < g[r] : g[l + h] < g[r + h]; } }; } sa; char buf[MAX_N]; long long cnt(long long n) { return n * (n + 1) / 2; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); freopen("err.txt", "w", stderr); #endif int n; long long ans; vector<pair<pair<int, int>, int>> v;//单调栈 scanf("%s", buf); n = (int) strlen(buf); sa.build(buf, n); sa.height[n + 1] = 0; sa.sa[n + 1] = n; ans = 0; for (int i = 1; i <= n + 1; ++i) { while (!v.empty() && v.back().first.second > sa.height[i]) { auto &end = v.back();// 某子串出现i-end.second次 if (end.first.first < sa.height[i])//队尾公共前缀长度小于当前公共前缀长度 {// 有(end.公共前缀长度减去当前公共前缀长度)种子串 ans += (end.first.second - sa.height[i]) * cnt(i - end.second); end.first.second = sa.height[i];//维护非严格单调增栈 } else { ans += (end.first.second - end.first.first) * cnt(i - end.second); v.pop_back();//维护非严格单调增栈 } } v.push_back({{sa.height[i], n - sa.sa[i]}, i}); } printf("%lld\n", ans); #ifndef ONLINE_JUDGE fclose(stdin); fclose(stderr); #endif return 0; }
Reference
译者的代码:https://searchcode.com/codesearch/view/30101319/
知识共享署名-非商业性使用-相同方式共享:码农场 » Codeforces 123D String 题解《挑战程序设计竞赛》