[UOJ#219][BZOJ4650][Noi2016]优秀的拆分
[UOJ#219][BZOJ4650][Noi2016]优秀的拆分
试题描述
输入
输出
输出 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]优秀的拆分的更多相关文章
- UOJ#219/BZOJ4650 [NOI2016]优秀的拆分 字符串 SA ST表
原文链接http://www.cnblogs.com/zhouzhendong/p/9025092.html 题目传送门 - UOJ#219 (推荐,题面清晰) 题目传送门 - BZOJ4650 题意 ...
- BZOJ4650 [NOI2016]优秀的拆分 【后缀数组】
题目 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆 分是优秀的.例如,对于字符串 aabaabaa,如果令 A=aabA=aa ...
- BZOJ4650: [Noi2016]优秀的拆分
考场上没秒的话多拿5分并不划算的样子. 思想其实很简单嘛. 要统计答案,求以每个位置开始和结束的AA串数量就好了.那么枚举AA中A的长度L,每L个字符设一个关键点,这样AA一定经过相邻的两个关键点.计 ...
- BZOJ4650 NOI2016优秀的拆分(后缀数组)
显然只要求出以每个位置开始的AA串数量就可以了,将其和反串同位置的结果乘一下,加起来就是答案.考虑对每种长度的字符串计数.若当前考虑的A串长度为x,我们每隔x个字符设一个关键点,求出相邻两关键点的后缀 ...
- [BZOJ4650][NOI2016]优秀的拆分(SAM构建SA)
关于解法这个讲的很清楚了,主要用了设关键点的巧妙思想. 主要想说的是一个刚学的方法:通过后缀自动机建立后缀树,再转成后缀数组. 后缀数组功能强大,但是最令人头疼的地方是模板太难背容易写错.用这个方法, ...
- bzoj千题计划317:bzoj4650: [Noi2016]优秀的拆分(后缀数组+差分)
https://www.lydsy.com/JudgeOnline/problem.php?id=4650 如果能够预处理出 suf[i] 以i结尾的形式为AA的子串个数 pre[i] 以i开头的形式 ...
- 题解【bzoj4650 [NOI2016]优秀的拆分】
Description 求对每一个连续字串将它切割成形如 AABB 的形式的方案数之和 Solution 显然 AABB 是由两个 AA 串拼起来的 考虑维护两个数组 a[i] 和 b[i] ,其中 ...
- BZOJ4650: [Noi2016]优秀的拆分(hash 调和级数)
题意 题目链接 Sol NOI的题都这么良心么.. 先交个\(n^4\)暴力 => 75 hash优化一下 => 90 然后\(90\)到\(100\)分之间至少差了\(10\)难度台阶= ...
- bzoj4650: [Noi2016]优秀的拆分 hash
好气啊,没开longlong又biubiu了 底层: 用hash或者奇奇怪怪的算法兹磁logn求最长公共前后缀 思路: 统计出从一个点开始和结束的形如AA的子串的个数 统计的时候把相邻的结果相乘加起来 ...
随机推荐
- block 应用说明
一.Block定义 Block可以理解为一个函数指针(即它是一个指针,指向某个函数) returnType (^blockName) (parameter list) = ^ (parameter l ...
- run_test() 验证平台的入口
Run,just run! ——阿甘正传 一个简单的例子: module tb_top; dut u_dut (); initial begin run_test(); end config ...
- Android学习总结(二)——Service基本概念和生命周期
好了,前面我们已经学习了Activity的知识,相信大家也有一定的理解,但是还是不能放松,Android四大组件,我们才学习了一个而已,接下我们继续学习Service.计划总结如下内容: 一.Serv ...
- 洛谷 P1926 小书童——刷题大军
题目背景 数学是火,点亮物理的灯:物理是灯,照亮化学的路:化学是路,通向生物的坑:生物是坑,埋葬学理的人. 文言是火,点亮历史宫灯:历史是灯,照亮社会之路:社会是路,通向哲学大坑:哲学是坑,埋葬文科生 ...
- (转)MyBatis框架的学习(二)——MyBatis架构与入门
http://blog.csdn.net/yerenyuan_pku/article/details/71699515 MyBatis框架的架构 MyBatis框架的架构如下图: 下面作简要概述: S ...
- solr scheme配置简介
solr 字段配置,和数据库数据索引配置 配置solr字段. schema.xml 文件里配置 先讲解一下,里面的一些字段 1. <types> ... </types> 表示 ...
- 在idea下创建maven
之前一直用eclipse,现在要用idea写一个安装过程玩玩 一:New Project 二:选择maven,在project SDK上选择你安装的jdk,默认安装在c:/Program Files ...
- python 判断路径是否存在
import os os.path.exists(文件绝对路径)
- python_112_网络编程 Socket编程
实例1:客户端发小写英文,服务器端返回给客户端大写英文(仅支持一次接受发送) 服务器端: #服务器端(先于客户端运行) import socket server=socket.socket() ser ...
- fossil 代理设置
C:\>fossil user new Joe C:\>fossil user default Joe 设置账户 fossil setting proxy http://-:-fossil ...