2020.11.9

\(\mathcal{A}\)
后缀数组

求法一般有\(O(nlogn)\)的倍增,\(O(n)\)的SA-IS,还有一个\(O(nlog^2n)\)的hash。其中hash可以动态用set维护。

\(ht[i]\)表示排名为\(i\)的后缀和排名为\(i-1\)的后缀的最长公共前缀长度。

  1. 单个字符串的相关问题的一个常用做法是先求后缀数组和 height数组,然后利用 height数组进行求解。

  2. 利用 height值对后缀进行分组的方法很常用,请读者认真体会。

给定一个字符串\(s\)
  • 求本质不同的子串个数。

    对于\(sa[i]\),假设排名前\(i-1\)个有\(t\)个本质不同的字串,以\(sa[i]\)开始的共有\(n - sa[i] + 1\)个,但其中有\(ht[i]\)个和\(sa[i - 1]\)重复,所有排名前\(i\)个共有\(t+n-sa[i]+1-ht[i]\)个,归纳得有\(\frac{n(n+1)}{2}-\sum ht[i]\)

  • 求最长的两个相同子串,要求不相交。

    二分答案。把\(ht[i]\geq mid\)的分成一组。就是把\(ht\)数组划分成若干段。每段间\(ht[i]\geq mid\)

    发现只有同一组之内的任意两个字串都满足\(\geq k\)的要求,而不同之见的肯定不满足。

    如果要不重复,只需\(sa[i]-sa[j]>=k\)。时间复杂度\(O(nlogn)\)

  • \(s\)所有循环同构的字符串的顺序。([JSOI2007]字符加密)

    从循环同构可以想到令\(s'=ss\)\(s'\)\(sa\)数组。所有开始位置\(\leq n\)的即为所有循环同构的字符串。

  • 求每个\(s[1..i]\)的本质不同的子串个数。([SDOI2016]生成魔咒)

    相当于每次增加一个字符,动态维护\(sa\)数组。可以用前面的hash算法,用set维护。问题在于如何实时维护\(ht\)数组。假设\(sa[k-1]<sa[k]\),注意到只有\(ht[k]=len-sa[k-1]+1\)并且下个读入的字符和\(s[sa[k] + len - sa[k-1]+1]\)相等时\(ht[k]\)需要\(+1\),(\(len\)表示当前读入的字符串的长度)这是没法优化的,每次修改,则总复杂度为\(O(n^2)\)。考虑把字符串倒置,每次往前面加字符。这样\(ht\)数组不会改变,而答案显然也正确。

    代码中的\(ht\)数组定义和先前的不同。\(ht[i]\)表示\(i\)开始的后缀和\(sa[rank[i]-1]\)开始的后缀的最长公共前缀长度。

    #include <bits/stdc++.h>
    using namespace std;
    namespace cxcyl {
    	const int mod = 1e9 + 7;
    	int n, mi[100005], a[200005], hash[200005], ht[200005];
    	long long sum;
    	inline int read() {
    		int x = 0, f = 1; char c = getchar();
    		while (c > -1 && c < '0' || c > '9') { if (c == '-') f = -1; c = getchar(); }
    		if (c == -1) return 0;
    		while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    		return x * f;
    	}
    	inline int equal(int x, int y) {
    		int l = 0, r = min(n - x + 1, n - y + 1), len = r;
    		if (x > y) swap(x, y);
    		while (l < r) {
    			int mid = l + 1 + r >> 1;
    			if ((1ll * mi[y - x] * (hash[x] - hash[x + mid]) % mod + mod) % mod == (hash[y] - hash[y + mid] + mod) % mod)
    				l = mid;
    			else
    				r = mid - 1;
    		}
    		return l;
    	}
    	struct _cmp {
    		inline bool operator()(int x, int y) {
    			int t = equal(x, y);
    			return a[x + t] < a[y + t];
    		}
    	};
    	set<int, _cmp> s;
    	inline int main() {
    		n = read();
    		mi[0] = 1;
    		for (int i = 1; i <= n; ++i)
    			mi[i] = 7ll * mi[i - 1] % mod;
    		for (int i = n; i >= 1; --i) {
    			a[i] = read();
    			hash[i] = (hash[i + 1] + 1ll * mi[i] * a[i]) % mod;
    			if (i < n) {
    				auto x = s.upper_bound(i);
    				if (x == s.end()) {
    					ht[i] = equal(i, *(--x));
    					sum += ht[i];
    				} else if (x == s.begin()) {
    					ht[*x] = equal(*x, i);
    					sum += ht[*x];
    				} else {
    					int v = *x, u = *(--x);
    					ht[i] = equal(u, i);
    					sum += ht[i];
    					sum -= ht[v];
    					ht[v] = equal(i, v);
    					sum += ht[v];
    				}
    			}
    			s.insert(i);
    			printf("%lld\n", (n - i + 2ll) * (n - i + 1) / 2 - sum);
    		}
    		return 0;
    	}
    } int main() { return cxcyl::main(); }
    

\(\mathcal{B}\)

关于整型溢出的问题。加入-ftrapv参数,在有溢出时会程序会崩溃。(Windows中把Dev-c++开成32位的才可用)
别用deque!据说空间会出乎意料的大。

posted @ 2020-11-09 19:12  chenyilei  阅读(88)  评论(0编辑  收藏  举报