zoukankan      html  css  js  c++  java
  • P1117 [NOI2016]优秀的拆分

    $ color{#0066ff}{ 题目描述 }$

    如果一个字符串可以被拆分为(AABB)的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的。

    例如,对于字符串(aabaabaa),如果令 (A=aab)(B=a),我们就找到了这个字符串拆分成 (AABB)的一种方式。

    一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 (A=a)(B=baa),也可以用 (AABB)表示出上述字符串;但是,字符串 (abaabaa) 就没有优秀的拆分。

    现在给出一个长度为 (n)的字符串(S),我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

    以下事项需要注意:

    1. 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。
    2. 在一个拆分中,允许出现(A=B)。例如 (cccc) 存在拆分(A=B=c)
    3. 字符串本身也是它的一个子串。

    (color{#0066ff}{输入格式})

    每个输入文件包含多组数据。

    输入的第一行只有一个整数(T),表示数据的组数。保证 (1≤T≤10)

    接下来 (T)行,每行包含一个仅由英文小写字母构成的字符串(S),意义如题所述。

    (color{#0066ff}{输出格式})

    输出 (T)行,每行包含一个整数,表示字符串(S) 所有子串的所有拆分中,总共有多少个是优秀的拆分。

    (color{#0066ff}{输入样例})

    4
    aabbbb
    cccccc
    aabaabaabaa
    bbaabaababaaba
    

    (color{#0066ff}{输出样例})

    3
    5
    4
    7
    

    (color{#0066ff}{数据范围与提示})

    我们用(S_{i,j})表示字符串 (S)(i)个字符到第(j)个字符的子串(从(1)开始计数)。

    第一组数据中,共有 (3)个子串存在优秀的拆分:

    (S_{1,4}=aabb),优秀的拆分为(A=a)(B=b)

    (S_{3,6}=bbbb),优秀的拆分为 (A=b)(B=b)

    (S_{1,6}=aabbbb),优秀的拆分为 (A=a)(B=bb)

    而剩下的子串不存在优秀的拆分,所以第一组数据的答案是 (3)

    第二组数据中,有两类,总共(4)个子串存在优秀的拆分:

    对于子串 (S_{1,4}=S_{2,5}=S_{3,6}=cccc),它们优秀的拆分相同,均为(A=c)(B=c),但由于这些子串位置不同,因此要计算(3) 次;

    对于子串 (S_{1,6}=cccccc),它优秀的拆分有 (2)种:(A=c),$B=cc $和 (A=cc)(B=c),它们是相同子串的不同拆分,也都要计入答案。

    所以第二组数据的答案是(3+2=5)

    第三组数据中,(S_{1,8})(S_{4,11}) 各有 (2) 种优秀的拆分,其中(S_{1,8}) 是问题描述中的例子,所以答案是(2+2=4)

    第四组数据中,(S_{1,4},S_{6,11},S_{7,12},S_{2,11},S_{1,8}) 各有 (1)种优秀的拆分,(S_{3,14})(2) 种优秀的拆分,所以答案是 (5+2=7)

    对于全部的测试点,保证(1≤T≤10)。以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的(T)组数据均满足限制条件。

    我们假定(n)为字符串(S)的长度,每个测试点的详细数据范围见下表:

    img

    (color{#0066ff}{题解})

    AA和BB本质上是一样的,都是两个相同的串连接,我们考虑枚举AA和BB中间的那个分界线,那么,以那个分界线为末尾的AA和以那个分界线为开头的BB的方案乘一下就是当前位置对答案的贡献

    我们可以发现, A和B的长度都不会超过(n/2)

    我们考虑分段来维护这个东西

    枚举段长从1到n的一半,枚举(1,len+1,2*len+1,3*len+1dots frac{n}{len}*len + 1)这些分界线

    统计两个分界线之间的贡献

    把字符串正反跑一遍SA,然后RMQ快速求出这两个分界线的向前LCPx和向后LCPy

    如果(x + y> =len + 1)那么就说明这是一个合法的拆分,中间重合的部分,就是l和r变化的范围

    在l和r的范围上差分一下就行了

    // luogu-judger-enable-o2
    #include<bits/stdc++.h>
    #define LL long long
    LL in() {
    	char ch; LL x = 0, f = 1;
    	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    	return x * f;
    }
    const int maxn = 5e4 + 10;
    int lg[maxn];
    struct SA {
    	char s[maxn];
    	int rk[maxn], sa[maxn], c[maxn], x[maxn], y[maxn], h[maxn], n, m, st[maxn][20];
    	void init() {
    		memset(x, 0, sizeof x);
    		memset(y, 0, sizeof y);
    		memset(h, 0, sizeof h);
    		memset(st, 0, sizeof st);
    		memset(sa, 0, sizeof sa);
    		memset(rk, 0, sizeof rk);
    		memset(c, 0, sizeof c);
    		for(int i = 1; i <= n; i++) x[i] = y[i] = h[i] = st[i][0] = sa[i] = rk[i] = 0;
    		for(int i = 1; i <= m; i++) c[i] = 0;
    		for(int i = 1; i <= n; i++) c[x[i] = s[i]]++;
    		for(int i = 1; i <= m; i++) c[i] += c[i - 1];
    		for(int i = n; i >= 1; i--) sa[c[x[i]]--] = i;
    		for(int k = 1; k <= n; k <<= 1) {
    			int num = 0;
    			for(int i = n - k + 1; i <= n; i++) y[++num] = i;
    			for(int i = 1; i <= n; i++) if(sa[i] > k) y[++num] = sa[i] - k;
    			for(int i = 1; i <= m; i++) c[i] = 0;
    			for(int i = 1; i <= n; i++) c[x[i]]++;
    			for(int i = 1; i <= m; i++) c[i] += c[i - 1];
    			for(int i = n; i >= 1; i--) sa[c[x[y[i]]]--] = y[i], y[i] = 0;
    			std::swap(x, y);
    			x[sa[1]] = 1, num = 1;
    			for(int i = 2; i <= n; i++) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k])? num : ++num;
    			if(num == n) break;
    			m = num;
    		}
    		for(int i = 1; i <= n; i++) rk[i] = x[i];
    		int H = 0;
    		for(int i = 1; i <= n; i++) {
    			if(rk[i] == 1) continue;
    			if(H) H--;
    			int j = sa[rk[i] - 1];
    			while(i + H <= n && j + H <= n && s[i + H] == s[j + H]) H++;
    			h[rk[i]] = H;
    		}
    		for(int i = 1; i <= n; i++) st[i][0] = h[i];
    		for(int j = 1; j <= 18; j++)
    			for(int i = 1; i + (1 << j) - 1 <= n; i++)
    				st[i][j] = std::min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    	}
    	int LCP(int x, int y) {
    		x = rk[x], y = rk[y];
    		if(x > y) std::swap(x, y);
    		x++;
    		int l = lg[y - x + 1];
    		return std::min(st[x][l], st[y - (1 << l) + 1][l]);
    	}
    	void ins(char *s, int len) {
    		n = len, m = 122;
    		for(int i = 1; i <= n; i++) this->s[i] = s[i];
    	}
    }A, B;
    LL pre[maxn], nxt[maxn];
    char s[maxn];
    int main() {
    	lg[0] = -1;
    	for(int i = 1; i < maxn; i++) lg[i] = lg[i >> 1] + 1;
    	for(int T = in(); T --> 0;) {
    		scanf("%s", s + 1);
    		int n = strlen(s + 1);
    		A.ins(s, n);
    		std::reverse(s + 1, s + n + 1);
    		B.ins(s, n);
    		A.init(), B.init();
    		memset(pre, 0, sizeof pre), memset(nxt, 0, sizeof nxt);
    		for(int L = 1; L <= n >> 1; L++)
    			for(int i = 1; i + L <= n; i += L) {
    				int j = i + L;
    				int xx = A.LCP(i, j), yy = B.LCP(n - i + 1, n - j + 1);
    				xx = std::min(xx, L), yy = std::min(yy, L);
    				if(xx + yy < L + 1) continue;
    				int nowlen = xx + yy - L - 1;
    				nxt[i - yy + 1]++, nxt[i - yy + nowlen + 2]--;
    				pre[j + xx - nowlen - 1]++, pre[j + xx]--;
    			}
    		for(int i = 1; i <= n; i++) pre[i] += pre[i - 1], nxt[i] += nxt[i - 1];
    		LL ans = 0;
    		for(int i = 1; i < n; i++) ans += pre[i] * nxt[i + 1];
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
  • 相关阅读:
    Linq to OBJECT延时标准查询操作符
    LINQ to XML
    动态Linq(结合反射)
    HDU 1242 dFS 找目标最短路
    HDu1241 DFS搜索
    hdu 1224 最长路
    BOJ 2773 第K个与m互质的数
    ZOJ 2562 反素数
    2016 ccpc 杭州赛区的总结
    bfs UESTC 381 Knight and Rook
  • 原文地址:https://www.cnblogs.com/olinr/p/10542545.html
Copyright © 2011-2022 走看看