Problem

Description

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

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

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

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

以下事项需要注意:

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

Input Format

每个输入文件包含多组数据。输入文件的第一行只有一个整数 \(T\),表示数据的组数。保证 \(1 \le T \le 10\)。

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

Output Format

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

Sample

input

4
aabbbb
cccccc
aabaabaabaa
bbaabaababaaba

output

3
5
4
7

Explanation

Explanation for Input

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

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

\(S[1,4] = \mathtt{aabb}\),优秀的拆分为 \(A=\mathtt{a}\),\(B = \mathtt{b}\);

\(S[3,6] = \mathtt{bbbb}\),优秀的拆分为 \(A=\mathtt{b}\),\(B = \mathtt{b}\);

\(S[1,6] = \mathtt{aabbbb}\),优秀的拆分为 \(A= \mathtt{a}\),\(B = \mathtt{bb}\)。

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

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

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

对于子串 \(S[1,6] = \mathrm{cccccc}\),它优秀的拆分有 \(2\) 种:\(A = \mathrm{c}\),\(B = \mathrm{cc}\) 和 \(A = \mathrm{cc}\),\(B = \mathrm{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\)。

Range

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

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

测试点编号 \(n\) 其他约束
1、2 \(\leq 300\) \(S\)中所有字符全部相同
3、4 \(\leq 2000\)
5、6 \(\leq 10\)
7、8 \(\leq 20\)
9、10 \(\leq 30\)
11、12 \(\leq 50\)
13、14 \(\leq 100\)
15 \(\leq 200\)
16 \(\leq 300\)
17 \(\leq 500\)
18 \(\leq 1000\)
19 \(\leq 2000\)
20 \(\leq 30000\)

Algorithm

后缀数组,差分。

Mentality

拿到题目先看看数据范围,嗯 \(......?\) 九十五分的 \(n^2\) 暴力算什么,那考场上谁会打正解, 看着就很显然啊,我们记录 \(a[i]\) 为以 \(i\) 结尾的形如 \(AA\) 的字符串的数量,\(b[i]\) 为以 \(i\) 开头的形如 \(AA\) 的字符串的数量,那么以 \(i\) 为 \(AA\) 与 \(BB\) 的分界线的答案不就是 \(a[i]*b[i+1]\) 吗 = =。

那 \(Ans=\sum a[i]*b[i+1]\) 就好了,计算每个 \(a[i]\) 的时候 \(O(n)\) 枚举 \(AA\) 串的起点或终点,利用 \(hash\) 来 \(O(1)\) 判断它能不能从中间分成相同的两个字符串就好。

\(n^2\) 有 \(95\) 分真的仙 \(......\)

那么我们接着考虑一下如何做 \(100\) 分。打表即可。

首先观察到,假设我们每隔 \(len\) 就标记一个点的话,若两个相邻的标记点之间的 后缀的最长公共前缀(\(LCP\))+前缀的最长公共后缀(\(LCS\)) \(\ge len\) 的话,就是如下情况:

当然,这里还有一个问题:如何求出 \(LCS\) 与 \(LCP\) 呢?其实很简单,我们来感性理解一下后缀数组中的 \(height\) 数组的含义。如果我们将所有后缀数组拿出来建一颗 \(Trie\) 树,那么 \(height\) 数组是不是可以理解为两个相邻叶子节点的 \(lca\) 的深度呢?那么根据 \(rmq\) 求 \(lca\) 的原理,两个后缀的最长公共前缀的长度,不就是它们之间的每两个叶子节点的 \(lca\) 中深度最小的那个,也就是夹在它们中间的 \(height\) 数组中最小的那个吗?

用 \(rmq\) 预处理即可 \(O(1)\) 查询两个后缀的最长公共前缀了。

继续来看上图,蓝色的即为 \(LCP\) ,红色部分为 \(LCS\) 。设它们的重合部分长度为 \(same\) ,对于这种情况,我们不难发现,对于 \(i-LCS\) 到 \(i-LCS+same\) 的位置,以它们为开头的长度为 \(2*len\) 的串必定为 \(AA\) 形式的串。

我觉得这甚至是不需要证明的,因为从中间分开必定相等,实在想不出来在纸上画一画就行。

那么同理,以 \(i-LCS+2*len-1\) 到 \(i-LCS+same+2*len-1\) 结尾的长度为 \(2*len\) 的串也必定为 \(AA\) 形式。

则我们对于相邻的两个距离为 \(len\) 的点,如果它们的 \(LCP+LCS\ge len\) ,那么对于 \(b[i-LCS]\) 至 \(b[i-LCS+same]\) 的位置,我们都加上 \(1\) ,对于 \(a[i-LCS+2*len-1]\) 至 \(a[i-LCS+same+2*len-1\) 的位置,我们也都加上一,这便是当前这两个距离为 \(len\) 的点对 \(a,b\) 数组的贡献了。

对于这个加上去的过程,我们可以使用差分。

那么答案就很明显可以直接统计了。

总结下来我们的步骤如下:

  • 枚举 \(len\) ,每隔 \(len\) 便标记一个点,这个的复杂度请百度 调和级数 ,复杂度为 \(nlog\) 。
  • 差分数组更改,复杂度 \(O(1)\)
  • 统计答案,复杂度 \(O(n)\)

则最后的时间复杂度为 \(O(nlog+n)=O(nlog)\) 。

完毕。

代码细节较多,建议稍稍看一下,不愧是当年的防 \(AK\) 题 \(QwQ\) 。

Code

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int p, T, n, M, cnt, tag[30001], sa[2][30001], rk[2][30001], h[2][30001],
tp[2][30001], rmq[2][30001][16], Log[40001], a[30002], b[30002];
long long ans;
char s[2][30002];
void Sort(int x) {
for (int i = 1; i <= M; i++) tag[i] = 0;
for (int i = 1; i <= n; i++) tag[rk[x][i]]++;
for (int i = 1; i <= M; i++) tag[i] += tag[i - 1];
for (int i = n; i >= 1; i--) sa[x][tag[rk[x][tp[x][i]]]--] = tp[x][i];
}
int find(int x, int l, int r) {
if (l > r) swap(l, r);
l++;
int L = Log[r - l + 1];
return min(rmq[x][l][L], rmq[x][r - (1 << L) + 1][L]);
}
int main() {
cin >> T;
int now = 1, num = -1, top = 2;
Log[1] = 0;
for (int i = 1; i <= 15; i++) {
now <<= 1, num++;
while (top <= now) Log[top++] = num;
} //预处理 log
while (T--) {
memset(a, 0, sizeof(a));
memset(b, 0, sizeof(b));
memset(rk, 0, sizeof(rk));
memset(tp, 0, sizeof(tp));
scanf("%s", s[0] + 1);
n = strlen(s[0] + 1);
for (int i = 1; i <= n; i++)
s[1][i] = s[0][n - i + 1]; //反过来方便处理前缀
M = 76;
for (int i = 1; i <= n; i++) rk[0][i] = s[0][i] - 'a' + 1, tp[0][i] = i;
for (int i = n; i >= 1; i--) rk[1][i] = s[1][i] - 'a' + 1, tp[1][i] = i;
Sort(0), Sort(1);
for (int l = 0; l < 2; l++) {
cnt = 0;
for (int len = 1; cnt < n; len <<= 1, M = cnt) {
cnt = 0;
for (int i = n - len + 1; i <= n; i++) tp[l][++cnt] = i;
for (int i = 1; i <= n; i++)
if (sa[l][i] > len) tp[l][++cnt] = sa[l][i] - len;
Sort(l);
swap(tp[l], rk[l]);
rk[l][sa[l][1]] = cnt = 1;
for (int i = 2; i <= n; i++)
rk[l][sa[l][i]] =
tp[l][sa[l][i]] == tp[l][sa[l][i - 1]] &&
tp[l][sa[l][i] + len] == tp[l][sa[l][i - 1] + len]
? cnt
: ++cnt;
}
} //后缀与前缀的排序
for (int l = 0; l < 2; l++) {
for (int i = 1, len = 0; i <= n; i++) {
if (len) len--;
int now = sa[l][rk[l][i] - 1];
while (s[l][now + len] == s[l][i + len]) len++;
rmq[l][rk[l][i]][0] = len;
}
for (int j = 1; j <= 15; j++)
for (int i = 1; i <= n; i++)
if (i + (1 << (j - 1)) <= n)
rmq[l][i][j] =
min(rmq[l][i][j - 1], rmq[l][i + (1 << (j - 1))][j - 1]);
} // height 数组与 rmq 预处理
for (int len = 1; len <= n / 2; len++)
for (int i = len; i + len <= n; i += len) {
int l = i, r = i + len;
int LCS = find(1, rk[1][n - r + 2], rk[1][n - l + 2]),
LCP = find(0, rk[0][l], rk[0][r]); //计算 LCS 与 LCP
LCS = min(LCS, len - 1),
LCP = min(
LCP,
len); //如果 LCS 或 LCP 过大,则会在其他点算重,所以和 len 取 min
if (LCS + LCP >= len) {
int same = LCS + LCP - len + 1;
b[l - LCS]++, b[l - LCS + same]--;
a[l - LCS + 2 * len - 1]++, a[l - LCS + same + 2 * len - 1]--;
} //差分数组更改
}
for (int i = 1; i <= n; i++)
a[i] += a[i - 1], b[i] += b[i - 1]; //计算差分数组真实值
ans = 0;
for (int i = 1; i < n; i++) ans += a[i] * b[i + 1]; //计算答案
cout << ans << endl;
}
}

【NOI 2016】优秀的拆分的更多相关文章

  1. [LOJ 2083][UOJ 219][BZOJ 4650][NOI 2016]优秀的拆分

    [LOJ 2083][UOJ 219][BZOJ 4650][NOI 2016]优秀的拆分 题意 给定一个字符串 \(S\), 求有多少种将 \(S\) 的子串拆分为形如 AABB 的拆分方案 \(| ...

  2. NOI 2016 优秀的拆分 (后缀数组+差分)

    题目大意:给你一个字符串,求所有子串的所有优秀拆分总和,优秀的拆分被定义为一个字符串可以被拆分成4个子串,形如$AABB$,其中$AA$相同,$BB$相同,$AB$也可以相同 作为一道国赛题,95分竟 ...

  3. 字符串(后缀自动机):NOI 2016 优秀的拆分

    [问题描述] 如果一个字符串可以被拆分为 AABB 的形式,其中 A 和 B 是任意非空字符串, 则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 aabaabaa,如果令 A = aab, B ...

  4. [NOI 2016]优秀的拆分

    Description 题库链接 给你一个长度为 \(n\) 的只含小写字母的字符串 \(S\) ,计算其子串有多少优秀的拆分. 如果一个字符串能被表示成 \(AABB\) 的形式,其中 \(A,B\ ...

  5. [bzoj 4650][NOI 2016]优秀的拆分

    传送门 Description 如果一个字符串可以被拆分为\(AABB\) 的形式,其中$ A$和 \(B\)是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串\(aabaaba ...

  6. 解题:NOI 2016 优秀的拆分

    题面 其实题目不算很难,但是我调试的时候被玄学了,for循环里不写空格会RE,写了才能过.神**调了一个多小时是这么个不知道是什么的玩意(真事,可以问i207M=.=),心态爆炸 发现我们只要找AA或 ...

  7. 【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

    [BZOJ4650][NOI2016]优秀的拆分(后缀数组) 题面 BZOJ Uoj 题解 如果我们知道以某个位置为开始/结尾的\(AA\)串的个数 那就直接做一下乘法就好 这个怎么求? 枚举一个位置 ...

  8. [BZOJ]4650 优秀的拆分(Noi2016)

    比较有意思的一道后缀数组题.(小C最近是和后缀数组淦上了?) 放在NOI的考场上.O(n^3)暴力80分,O(n^2)暴力95分…… 即使想把它作为一道签到题也不要这么随便啊摔(╯‵□′)╯︵┻━┻ ...

  9. UOJ#219. 【NOI2016】优秀的拆分 [后缀数组 ST表]

    #219. [NOI2016]优秀的拆分 题意:求有多少AABB样子的子串,拆分不同的同一个子串算多个 一开始一直想直接求,并不方便 然后看了一眼Claris的题解的第一行就有思路了 如果分开,求\( ...

随机推荐

  1. Hive 数仓中常见的日期转换操作

    (1)Hive 数仓中一些常用的dt与日期的转换操作 下面总结了自己工作中经常用到的一些日期转换,这类日期转换经常用于报表的时间粒度和统计周期的控制中 日期变换: (1)dt转日期 to_date(f ...

  2. 配置开发环境2——eclipse配置

    纯手动配置eclipse, Eclipse配置 配置工作空间的编码方式 General—Workspace:改成Other:UTF-8 配置property的编码方式 配置maven Window — ...

  3. AWS免费云服务套餐申请步骤及常见问题

    AWS免费云服务套餐申请步骤及常见问题 AWS免费使用套餐常见问题_AWS免费云服务套餐_-AWS云服务https://amazonaws-china.com/cn/free/faqs/ 什么是 AW ...

  4. AtCoder Regular Contest 077 C - pushpush

    题目链接:http://arc077.contest.atcoder.jp/tasks/arc077_a Time limit : 2sec / Memory limit : 256MB Score ...

  5. JustOj 1414: 潘神的排序

    题目描述 老潘,袁少,小艾都是江理的大个子,他们想按身高排队,现在给你他们的身高,请你算出队伍中站在第二的有多高. 输入 输入三个整数,分别表示三个人的身高.(单位:纳米) 输出 输出身高排第二的身高 ...

  6. .NET 常用ORM之iBatis

    ibatis 一词来源于“internet”和“abatis”的组合,是一个由Clinton Begin在2001年发起的开放源代码项目,到后面发展的版本叫MyBatis但都是指的同一个东西.最初侧重 ...

  7. nginx负载均衡六种策略

    Nginx服务器之负载均衡策略(6种)   一.关于Nginx的负载均衡 在服务器集群中,Nginx起到一个代理服务器的角色(即反向代理),为了避免单独一个服务器压力过大,将来自用户的请求转发给不同的 ...

  8. 使用Selenium+firefox抓取网页指定firefox_profile后的问题

    from: https://blog.csdn.net/chufazhe/article/details/51145834 摘要:在使用selenium和firefox抓取网页指定firefox_pr ...

  9. kswapd0 进程CPU占用过高

    前几天遇到的一个问题,自己本地用VM配置的虚拟机,一般会top查看进程以及CPU占用的一些情况.又一次用laravel 打印对象,里面的内容比较多,浏览器当时就卡了. 然后看进程的情况.我以为会是ng ...

  10. dubbo rpc调用抛出的Exception处理

    关于dubbo的Exception堆栈被吃处理,网上已经有比较多的解决方法,在我们的应用场景中,不希望RPC调用对方抛出业务exception,而是通过Resp中的errorCode,errorMsg ...