zoukankan      html  css  js  c++  java
  • [LOJ#3311]「ZJOI2020」字符串

    前沿字符串神题 QAQ

    题目链接

    题目传送门

    简要算法

    字符串理论、最长公共前缀(LCP)、容斥、扫描线、BIT

    前置定理

    定义一个串 (S) 为「本原平方串」,当且仅当 (S) 能够写成 (AA) 的形式,并且 (A) 不是循环串。换句话说,(S) 是本原平方串,当且仅当 (S) 只有一个长度为 (frac{|S|}2) 的循环节(这里的循环必须是整数次)。

    结论:一个长度为 (n) 的串中,位置不同的本原平方子串个数不超过 (O(nlog n))

    证明:

    (S) 为本原平方串且 (|S|=2r),且 (S) 存在一个长度大于 (r) 的本原平方串作为严格后缀,其长度为 (2r'),则根据对称性及 border-period 理论可得 (S) 的一半符合 (AA'AAAdots A) 的形式,其中 (A')(A) 的一段后缀,(|A|=r-r')
    (3(r-r')<r) 也就是 (r'>frac 23r),这时 (AA'AAAdots A)(A') 之后的 (A) 至少重复了两次。设 (S) 还存在一个本原平方串作为后缀,其长度 (2t) 满足 (r'<t<r),则 (S) 的一半还符合 (BB'BBBdots B),其中 (|B|=r-t),这时串 (AA) 同时存在 (|A|)(|B|) 两个 period,由 (|B|<|A|)(AA) 存在 period (gcd(|A|,|B|)),即 (A) 是循环串。
    (A)(B) 公共的最小循环节为 (T),我们尝试证明:(|A'|)(T) 的倍数。
    考虑反证法,由于找出了最小循环节 (T),故假设 (S) 的一半符合 (TTdots TT'TTdots T)(T')(T) 的严格后缀),(T') 左边的总串长为 (|A|)
    这时 (B') 必然符合 (T'TTdots T),又由于 (|B|<|A|),故会有一个 (T) 匹配上 (B') 的前缀,也就是说 (T) 存在一个不为本身的循环位移和本身相同,这与 (T) 为最小循环串矛盾。得证。
    这时我们得出 (S) 的一半存在循环节 (T),这与 (S) 为本原平方串矛盾。于是对于长度为 (n) 的本原平方串 (S),最多存在一个长度大于 (frac 23n) 的本原平方串作为后缀。
    也就是说,对一个串不断找出最长本原平方串后缀,每找出 (2) 个之后长度必然小于原来的 (frac 23),即一个串最多有 (O(log n)) 个本原平方串后缀。
    证毕。

    Solution

    先考虑找出所有的三元组 ((l,r,T)),满足 (S[l:r]) 的最小 period 为 (T)(2Tle r-l+1)。换句话说,(S[l:r]) 的所有长度为 (2T) 的倍数的子串都是平方串,其中长度为 (2T) 的子串为本原平方串。

    方法即为枚举 (T)(ige 0) 满足 (T|i,i+2Tle n),使用 NOI2016 优秀的拆分 那题的做法,设 (lt=min(T, ext{lcs}(S[1:i+T],S[1:i+2T])))(rt=min(T-1, ext{lcp}(S[i+T+1:n],S[i+2T+1:n]))),若 (lt+rtge T),则右端点在区间 ([i+3T-lt,i+2T+rt]) 内,长度为 (2T) 的所有子串都是平方串。然后对于同一个 (T),把这些区间合并起来(如果一个区间的右端点加一等于另一个区间的左端点),就求出了所有三元组。

    考虑一个三元组对答案的贡献,先考虑长度为 (2T) 的串。

    如果不考虑重复,则对于任意 (lle ile r-2T+1),串 (S[i:i+2T-1]) 都是一个合法串,如果是 (S[L:R]) 的子串就对询问 ([L,R]) 贡献 (1)

    考虑一个三元组内出现重复子串怎么处理,根据本原串的性质,(S[l:r]) 的两个子串 (S[i:i+T-1]=S[j:j+T-1]) 当且仅当 (T|j-i)。所以考虑容斥,类似于 (点数-边数=1),如果两个距离为 (T),长度均为 (2T) 的串都是 (S[L:R]) 的子串,就贡献 (-1),也就是对于任意 (lle ile r-3T+1),如果 (S[i:i+3T-1])(S[L:R]) 的子串就贡献 (-1)

    长度为 (4T,6T) 的串同理。注意枚举串长的次数一定不超过本原平方串的个数,为 (O(nlog n)) 级别。

    还有一个问题就是不同的三元组产生的相同子串。仍然考虑容斥,设有三元组 ((a,b,T)(c,d,T)) 满足 (b<d),长度为 (2kT)(k) 为正整数)的串 (s)(S[a:b])最后一次出现的左端点(x)(S[c:d])第一次出现的右端点(y),则可以在 ([x,y]subseteq[L,R]) 时把答案减掉 (1)

    注意到需要枚举的 (s) 的个数和本原平方串的个数相等,均为 (O(nlog n))。故可以外层枚举 (T),按右端点从小到大枚举三元组,用 hash+map 维护每种子串最后出现的位置,遇到一个三元组 ((l,r,T)) 时,枚举 (l'in[l,l+T-1])(2T|r'-l'+1)(r'le r),即找到了一个 (s=S[l':r']),设 (s) 上次出现的左端点为 (lst),则我们找到了一个 ([x,y])([lst,r'])。然后把所有满足 (r'in[r-T+1,r])(2T|r'-l'+1)(l'ge l) 的子串 (S[l':r']) 加入 map

    问题就转化成二维平面上加入一些点,再加入一组形如 ((x,y)(x+1,y+1)(x+2,y+2)dots) 的点组,之后每次矩形查询。

    扫描线 + BIT 即可。(O(nlog^2n))

    Code

    以下代码使用了二分 hash 求最长公共前后缀。

    #include <bits/stdc++.h>
    
    template <class T>
    inline void read(T &res)
    {
    	res = 0; bool bo = 0; char c;
    	while (((c = getchar()) < '0' || c > '9') && c != '-');
    	if (c == '-') bo = 1; else res = c - 48;
    	while ((c = getchar()) >= '0' && c <= '9')
    		res = (res << 3) + (res << 1) + (c - 48);
    	if (bo) res = ~res + 1;
    }
    
    template <class T>
    inline T Min(const T &a, const T &b) {return a < b ? a : b;}
    
    typedef long long ll;
    
    const int N = 2e5 + 5, E = 19, djq = 1e9 + 7, zyy = 1e9 + 9;
    
    int n, q, w[N], Log[N], ml[N], p1[N], p2[N], h1[N], h2[N];
    char s[N];
    ll ans[N];
    std::map<ll, int> occ;
    
    struct interv
    {
    	int T, l, r;
    } arr[N];
    
    struct query
    {
    	int t, l, r, id, v;
    	
    	friend inline bool operator < (query a, query b)
    	{
    		return a.t < b.t;
    	}
    };
    
    std::vector<query> que;
    
    struct BIT
    {
    	ll A[N];
    	
    	void change(int x, ll v)
    	{
    		for (; x <= n; x += x & -x)
    			A[x] += v;
    	}
    	
    	ll ask(int x)
    	{
    		ll res = 0;
    		for (; x; x -= x & -x) res += A[x];
    		return res;
    	}
    } C, CX, CY, CI, CT, XY;
    
    struct modify
    {
    	int x, y, v;
    	
    	friend inline bool operator < (modify a, modify b)
    	{
    		return a.x < b.x;
    	}
    };
    
    std::vector<modify> a1, a2;
    
    ll nealchen(int l, int r)
    {
    	int res1 = (h1[r] - 1ll * h1[l - 1] * p1[r - l + 1] % djq + djq) % djq,
    		res2 = (h2[r] - 1ll * h2[l - 1] * p2[r - l + 1] % zyy + zyy) % zyy;
    	return 1ll * res1 * zyy + res2;
    }
    
    int lcpA(int x, int y)
    {
    	int l = 1, r = Min(n - x + 1, n - y + 1);
    	while (l <= r)
    	{
    		int mid = l + r >> 1;
    		if (nealchen(x, x + mid - 1) == nealchen(y, y + mid - 1)) l = mid + 1;
    		else r = mid - 1;
    	}
    	return r;
    }
    
    int lcpB(int x, int y)
    {
    	int l = 1, r = Min(x, y);
    	while (l <= r)
    	{
    		int mid = l + r >> 1;
    		if (nealchen(x - mid + 1, x) == nealchen(y - mid + 1, y)) l = mid + 1;
    		else r = mid - 1;
    	}
    	return r;
    }
    
    ll nctxdy(int k, int r)
    {
    	int p = k - r; if (p < 0) p = 0; ll tmp = C.ask(p);
    	return tmp * r - CX.ask(p) + (C.ask(n) - tmp) * k - CY.ask(n - p - 1)
    		- CI.ask(n - k) * k + CT.ask(n - k);
    }
    
    int main()
    {
    	int l, r;
    	scanf("%d%d%s", &n, &q, s + 1); Log[0] = -1;
    	p1[0] = p2[0] = 1;
    	for (int i = 1; i <= n; i++)
    	{
    		p1[i] = 20050131ll * p1[i - 1] % djq;
    		p2[i] = 1312005ll * p2[i - 1] % zyy;
    		h1[i] = (20050131ll * h1[i - 1] + s[i] - 'a' + 1) % djq;
    		h2[i] = (1312005ll * h2[i - 1] + s[i] - 'a' + 1) % zyy;
    	}
    	for (int i = 1; i <= q; i++)
    	{
    		read(l); read(r);
    		que.push_back((query) {r, l, r, i, 1});
    		if (l > 1) que.push_back((query) {l - 1, l, r, i, -1});
    	}
    	for (int i = 1; i <= n; i++) Log[i] = Log[i >> 1] + 1;
    	memset(ml, 0x3f, sizeof(ml));
    	std::sort(que.begin(), que.end());
    	for (int T = 1; (T << 1) <= n; T++)
    	{
    		int tot = 0;
    		for (int i = 0; i + (T << 1) <= n; i += T)
    		{
    			int lt = Min(T, lcpB(i + T, i + (T << 1))),
    				rt = Min(T - 1, lcpA(i + T + 1, i + (T << 1) + 1));
    			if (lt + rt >= T)
    			{
    				int r = i + (T << 1) + rt, l = r - (lt + rt - T);
    				if (!tot || arr[tot].r + 1 < l) arr[++tot] = (interv) {T, l, r};
    				else arr[tot].r = r;
    			}
    		}
    		occ.clear();
    		for (int i = 1; i <= tot; i++)
    		{
    			int l = arr[i].l, r = arr[i].r;
    			if (ml[r] <= l - (T << 1) + 1) continue;
    			for (int j = l; j <= r; j++) ml[j] = Min(ml[j], l - (T << 1) + 1);
    			l -= (T << 1) - 1;
    			for (int x = (T << 1); x <= r - l + 1; x += T)
    			{
    				a1.push_back((modify) {l, l + x - 1, x / T & 1 ? -1 : 1});
    				if (r < n) a1.push_back((modify)
    					{r - x + 2, r + 1, x / T & 1 ? 1 : -1});
    				if (!(x / T & 1))
    				{
    					for (int i = l; i < l + T && i + x - 1 <= r; i++)
    					{
    						int nxt = occ[nealchen(i, i + x - 1)];
    						if (nxt) a2.push_back((modify) {nxt, i + x - 1, -1});
    					}
    					for (int i = r; i > r - T && i - x + 1 >= l; i--)
    						occ[nealchen(i - x + 1, i)] = i - x + 1;
    				}
    			}
    		}
    	}
    	std::sort(a1.begin(), a1.end()); std::sort(a2.begin(), a2.end());
    	for (int i = 0, j = 0, k = 0; i < que.size(); i++)
    	{
    		while (j < a1.size() && a1[j].x <= que[i].t)
    			C.change(a1[j].y - a1[j].x, a1[j].v),
    			CX.change(a1[j].y - a1[j].x, (a1[j].x - 1) * a1[j].v),
    			CY.change(n - (a1[j].y - a1[j].x), (a1[j].y - 1) * a1[j].v),
    			CI.change(n - a1[j].y + 1, a1[j].v),
    			CT.change(n - a1[j].y + 1, (a1[j].y - 1) * a1[j].v), j++;
    		while (k < a2.size() && a2[k].x <= que[i].t)
    			XY.change(a2[k].y, a2[k].v), k++;
    		ans[que[i].id] += (nctxdy(que[i].r, que[i].t) - nctxdy(que[i].l - 1, que[i].t)
    			+ XY.ask(que[i].r) - XY.ask(que[i].l - 1)) * que[i].v;
    	}
    	for (int i = 1; i <= q; i++) printf("%lld
    ", ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    机器学习入门:线性回归及梯度下降
    torch7入门(安装与使用)
    机器学习--详解人脸对齐算法SDM-LBF
    人脸对齐和应用
    如何使用Unity制作虚拟导览(一)
    fatal error C1083: Cannot open include file: 'qttreepropertybrowser.moc': No such file or directory
    在QTreeWidget中删除QTreeWidgetItem
    如何写一个简单的手写识别算法?
    面向对象编程的弊端是什么?
    神舟飞船上的计算机使用什么操作系统,为什么是自研发不是 Linux?
  • 原文地址:https://www.cnblogs.com/xyz32768/p/13258489.html
Copyright © 2011-2022 走看看