[UOJ#219][BZOJ4650][Noi2016]优秀的拆分

试题描述

如果一个字符串可以被拆分为 AABBAABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。例如,对于字符串 aabaabaa,如果令 A=aab,B=a,我们就找到了这个字符串拆分成 AABBAABB 的一种方式。一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 A=a,B=baa,也可以用 AABBAABB 表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。现在给出一个长度为 n 的字符串 S,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。以下事项需要注意:出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。在一个拆分中,允许出现 A=B。例如 cccc 存在拆分 A=B=c。字符串本身也是它的一个子串。

输入

每个输入文件包含多组数据。输入文件的第一行只有一个整数 T,表示数据的组数。保证 1≤T≤10。接
下来 T 行,每行包含一个仅由英文小写字母构成的字符串 S,意义如题所述。

输出

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

输入示例

aabbbb
cccccc
aabaabaabaa
bbaabaababaaba

输出示例


数据规模及约定

|S|≤30000,1≤T≤10

题解

这题貌似是少有的后缀自动机不能解决而后缀数组能解决的题目之一了。

此题做法:后缀数组 + 调和级数。

我们发现问题其实就是找到所有形如 AA 的串(即前后两半相同的串),把它所在的左、右端点位置上计数器 + 1 即可。

首先正反串都建一个后缀数组,这样方便操作。

接下来,我们不妨枚举 A 的长度 len,然后把字符串按照 len 划分成若干块,然后我们需要处理左端点在每一块中的形如 AA 的子串,假设我们当前处理的位置是 i(见下图)。

注意这张图中,我们当前位置为 i,处理开头在最左边那个块中的形如 AA 的子串。(块与块之间的分隔符是长竖线,以下“形如 AA 的子串”均简称为“AA 子串”)

令 L1 = LCP(i, i+len)(LCP 为最长公共前缀,LCS 为最长公共后缀),那么存在 AA 子串的充分必要条件是 LCS(i+len-1, i-1) > 0 且 LCS(i+len-1, i-1) + L1 >= len(否则红色区域就会有不同的字符,那么显然不可能存在 AA 子串)。那么接下来的问题就好办了,令 L2 = LCS(i+L1-len-1, i+L1-1),不难发现区间 [ i+L1-len-L2, i+L1-len ] 中的位置都是长度为 len 的 AA 子串的左端点(至于右端点在哪,请读者思考)。(然而这里并不用线段树实现区间加,可以直接打离线标记)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std; int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
} #define maxn 30010
#define maxlog 15
#define LL long long char Str[maxn];
int n, Log[maxn];
struct SA {
char S[maxn];
int n, sa[maxn], Ws[maxn], rank[maxn], height[maxn], mnh[maxlog][maxn]; void init(char* str) {
strcpy(S + 1, str); n = strlen(S + 1);
return ;
} bool cmp(int* a, int p1, int p2, int l) {
if(p1 + l > n && p2 + l > n) return a[p1] == a[p2];
if(p1 + l > n || p2 + l > n) return 0;
return a[p1] == a[p2] && a[p1+l] == a[p2+l];
}
void ssort() {
int *x = rank, *y = height, m = 0;
memset(Ws, 0, sizeof(Ws));
for(int i = 1; i <= n; i++) Ws[x[i] = S[i]]++, m = max(m, x[i]);
for(int i = 1; i <= m; i++) Ws[i] += Ws[i-1];
for(int i = n; i; i--) sa[Ws[x[i]]--] = i;
for(int j = 1, pos; pos < n; j <<= 1, m = pos) {
pos = 0;
for(int i = n - j + 1; i <= n; i++) y[++pos] = i;
for(int i = 1; i <= n; i++) if(sa[i] > j) y[++pos] = sa[i] - j;
for(int i = 1; i <= m; i++) Ws[i] = 0;
for(int i = 1; i <= n; i++) Ws[x[i]]++;
for(int i = 1; i <= m; i++) Ws[i] += Ws[i-1];
for(int i = n; i; i--) sa[Ws[x[y[i]]]--] = y[i];
swap(x, y); pos = 1; x[sa[1]] = 1;
for(int i = 2; i <= n; i++) x[sa[i]] = cmp(y, sa[i], sa[i-1], j) ? pos : ++pos;
}
return ;
} void calch() {
for(int i = 1; i <= n; i++) rank[sa[i]] = i;
for(int i = 1, j, k = 0; i <= n; height[rank[i++]] = k)
for(k ? k-- : 0, j = sa[rank[i]-1]; S[i+k] == S[j+k]; k++);
return ;
} void rmq_init() {
Log[1] = 0;
for(int i = 2; i <= n; i++) Log[i] = Log[i>>1] + 1;
for(int i = 1; i <= n; i++) mnh[0][i] = height[i];
for(int j = 1; (1 << j) <= n; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
mnh[j][i] = min(mnh[j-1][i], mnh[j-1][i+(1<<j-1)]);
return ;
}
int query(int p1, int p2) {
if(p1 < 1 || p1 > n || p2 < 1 || p2 > n) return 0;
int l = rank[p1], r = rank[p2];
if(l > r) swap(l, r); l++;
if(l > r) return n;
int t = Log[r-l+1];
return min(mnh[t][l], mnh[t][r-(1<<t)+1]);
} void _debug() {
for(int i = 1; i <= n; i++) printf("%d%c", sa[i], i < n ? ' ' : '\n');
for(int i = 2; i <= n; i++) printf("%d%c", height[i], i < n ? ' ' : '\n');
return ;
}
} sol, resol; #define repos(i) n - (i) + 1 int totl[maxn], totr[maxn];
void Addl(int l, int r) {
if(l < 1) l = 1;
if(r > n) r = n;
if(l > r) return ;
totl[l]++; totl[r+1]--;
return ;
}
void Addr(int l, int r) {
if(l < 1) l = 1;
if(r > n) r = n;
if(l > r) return ;
totr[l]++; totr[r+1]--;
return ;
} int main() {
int T = read();
while(T--) {
scanf("%s", Str);
n = strlen(Str);
Str[n++] = 'A'; Str[n] = '\0'; sol.init(Str);
sol.ssort();
sol.calch();
sol.rmq_init();
for(int i = 0; i < (n >> 1); i++) swap(Str[i], Str[n-i-1]);
resol.init(Str);
resol.ssort();
resol.calch();
resol.rmq_init();
memset(totl, 0, sizeof(totl));
memset(totr, 0, sizeof(totr));
for(int len = 1; len < n; len++)
for(int i = len + 1; i + len <= n; i += len) {
int l1 = sol.query(i, i + len), l2 = resol.query(repos(i + len - 1), repos(i - 1));
// printf("%d %d L1, L2: %d %d\n", len, i, l1, l2);
if(!l2 || l1 + l2 < len) continue;
l1 = min(l1, len - 1);
int r = i + l1 - 1; l2 = min(resol.query(repos(r), repos(r - len)), l1);
// printf("[%d] %d (%d, %d) %d\n", len, r, repos(r), repos(r - len), l2);
// printf("addr: %d %d | %d %d\n", r + len - l2, r + len, len, i);
Addr(r + len - l2, r + len);
Addl(r - len - l2 + 1, r - len + 1);
}
for(int i = 1; i <= n; i++) totl[i] += totl[i-1], totr[i] += totr[i-1];
// for(int i = 1; i <= n; i++) printf("%d %d\n", totl[i], totr[i]); LL ans = 0;
for(int i = 1; i < n; i++) ans += (LL)totr[i] * totl[i+1];
printf("%lld\n", ans);
} return 0;
}

[UOJ#219][BZOJ4650][Noi2016]优秀的拆分的更多相关文章

  1. UOJ#219/BZOJ4650 [NOI2016]优秀的拆分 字符串 SA ST表

    原文链接http://www.cnblogs.com/zhouzhendong/p/9025092.html 题目传送门 - UOJ#219 (推荐,题面清晰) 题目传送门 - BZOJ4650 题意 ...

  2. BZOJ4650 [NOI2016]优秀的拆分 【后缀数组】

    题目 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆 分是优秀的.例如,对于字符串 aabaabaa,如果令 A=aabA=aa ...

  3. BZOJ4650: [Noi2016]优秀的拆分

    考场上没秒的话多拿5分并不划算的样子. 思想其实很简单嘛. 要统计答案,求以每个位置开始和结束的AA串数量就好了.那么枚举AA中A的长度L,每L个字符设一个关键点,这样AA一定经过相邻的两个关键点.计 ...

  4. BZOJ4650 NOI2016优秀的拆分(后缀数组)

    显然只要求出以每个位置开始的AA串数量就可以了,将其和反串同位置的结果乘一下,加起来就是答案.考虑对每种长度的字符串计数.若当前考虑的A串长度为x,我们每隔x个字符设一个关键点,求出相邻两关键点的后缀 ...

  5. [BZOJ4650][NOI2016]优秀的拆分(SAM构建SA)

    关于解法这个讲的很清楚了,主要用了设关键点的巧妙思想. 主要想说的是一个刚学的方法:通过后缀自动机建立后缀树,再转成后缀数组. 后缀数组功能强大,但是最令人头疼的地方是模板太难背容易写错.用这个方法, ...

  6. bzoj千题计划317:bzoj4650: [Noi2016]优秀的拆分(后缀数组+差分)

    https://www.lydsy.com/JudgeOnline/problem.php?id=4650 如果能够预处理出 suf[i] 以i结尾的形式为AA的子串个数 pre[i] 以i开头的形式 ...

  7. 题解【bzoj4650 [NOI2016]优秀的拆分】

    Description 求对每一个连续字串将它切割成形如 AABB 的形式的方案数之和 Solution 显然 AABB 是由两个 AA 串拼起来的 考虑维护两个数组 a[i] 和 b[i] ,其中 ...

  8. BZOJ4650: [Noi2016]优秀的拆分(hash 调和级数)

    题意 题目链接 Sol NOI的题都这么良心么.. 先交个\(n^4\)暴力 => 75 hash优化一下 => 90 然后\(90\)到\(100\)分之间至少差了\(10\)难度台阶= ...

  9. bzoj4650: [Noi2016]优秀的拆分 hash

    好气啊,没开longlong又biubiu了 底层: 用hash或者奇奇怪怪的算法兹磁logn求最长公共前后缀 思路: 统计出从一个点开始和结束的形如AA的子串的个数 统计的时候把相邻的结果相乘加起来 ...

随机推荐

  1. MAC无法确认开发者身份

    网上下载的软件,如果来自身份不明的开发者,在MAC上打开时会提示无法确认开发者的身份,在网上找到了一篇尝试解决的文章,文章链接地址为http://jingyan.baidu.com/article/f ...

  2. uvm_void 寂静的空宇

    空也是一种存在.   ——<三体> 文件: $UVM_HOME/src/base/uvm_misc.svh   virtual class uvm_void; endclass   在静寂 ...

  3. 查询sqlserver数据库,表占用数据大小

     if exists(select 1 from tempdb..sysobjects where id=object_id('tempdb..#tabName') and xtype='u')dro ...

  4. 远程linux服务器mysql数据库导入和导出.sql文件

    大部分情况本地开发环境为windows,部署的服务器为Linux,本地数据库导出.sql文件后需要远程导入服务器,具体如下. 首先连接服务器,即服务器ip,协议,端口,用户名及密码,可以通过ftp客户 ...

  5. openstack No valid host was found. There are not enough hosts available.

    root@dell-PowerEdge-T30:~# gedit /var/log/nova/nova-conductor.logroot@dell-PowerEdge-T30:~# gedit /v ...

  6. 推荐一个yaml文件转json文件的在线工具

    YAML的全称是YAML Ain't Markup Language,是一种简洁的非标记语言,以数据为中心,使用空白,缩进,和分行组织数据,从而使得表示更加简洁易读. YAML如今广泛应用于微服务开发 ...

  7. JSON数组不用字符串转换的写法

    var organization = []; //机构组织 //初始化用户数据列表中用户机构列的数据源 admin.ajax("GetOrganizationInfo", null ...

  8. 【Qt】2.2 继续了解信号和槽

    槽和普通成员函数一样,可以是虚函数.被重载,可以是公有.私有.保护的.它可以被其它C++成员函数调用. 槽连接了信号,当发射这个信号时,槽会被自动调用. 连接函数: bool QObject::con ...

  9. CPP-基础:关于多态

        类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节的内容会觉得不习惯,因为很多人错误的认为,支持类的封装的语言就是支持面向对象的,其实不然,Visua ...

  10. Jarvis OJ-Smashes

    栈溢出之利用-stack-chk-fail from pwn import * old_flag_addr = 0x600d20 new_flag_addr = 0x400d20 #p = proce ...