题解【bzoj4650 [NOI2016]优秀的拆分】
Description
求对每一个连续字串将它切割成形如 AABB 的形式的方案数之和
Solution
显然 AABB 是由两个 AA 串拼起来的
考虑维护两个数组 a[i] 和 b[i] ,其中 a[i] 表示以 \(i\) 结尾有多少个 AA 串,b[i] 表示以 \(i\) 开头有多少个 AA 串
最后答案就是 \(\sum \limits _{i=1}^{n-1}a[i]b[i+1]\) (就是两个串拼起来)
如何求 a[i] 和 b[i] 呢?
首先有一个非常显然的 n^2 哈希做法(对于每一个 \(i\) 用 \(j\) 扫一遍用哈希判断有几个 AA 串),有 95 分!
如何拿到最后的 5 分呢?考虑枚举一个 Len ,然后对于每个点求出他是否是一个 2 * Len 的 AA 串的开头 / 结尾。
我们每隔 Len 放一个点,这样每一个 长度为 2 * Len 的 AA 串都至少会经过两个相邻的点。
所以再转换为每两个相邻的点会对 a, b 产生多少贡献。
先求出这对相邻点所代表的前缀的最长公共后缀 LCS 和 所代表的后缀的最长公共前缀 LCP
如果 LCP + LCS < Len 就下面这种情况:
其中两个红线是关键点(相距为 Len),蓝线是LCS,绿线是LCP,LCP+LCS < Len
则有
这条紫线就是第一个可能满足条件的 AA 串
但此时我们会发现下图
其中两个红色荧光笔的部分在 AA 串中是对应的,但他们至少有一个位置并不相同 (不然LCP可以再长)
所以此时不会有任意一个长度为 2 * Len 的 AA 串满足条件。
如果 LCP + LCS >= Len 就有下面这种情况
此时中间必然就没有空隙。可以发现:
粉色的是第一个 AA 串,可以发现它是可以分成两个相同的 A 串的(可以理解成中间没有缝隙了所以就没有不一样的了)
然后这个 AA 串可以一直往后滑动,每滑动一个位置都可以形成一个新的 AA 串知道 AA 串的后端点滑动到最右边的绿色端点。也就是滑动到棕色 AA 串
此时可以发现,每一个存在于红色荧光部分的点都可以作为一个新的 AA 串的开头
同理,每一个再绿色荧光笔的点可以作为一个新的 AA 串的结尾。
于是就将红色荧光笔的区间的 b 加上 1,绿色的 a 加上 1,就大功告成。
如何实现这个过程呢?复杂度是什么呢?
- 枚举 Len ,每隔 Len 设置关键点:这个的复杂度是调和级数 \(O(n \log n)\)
- 求 后缀LCP,前缀LCS:使用后缀数组 + st 表 做到 O(1) 查询
- 区间加上 1 : 差分维护就可以了。
至此,此题完结
Code
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1001000;
int T;
ll a[N], b[N];
struct SuffixArray {
char S[N]; int n;
int cnt[N], sa[N], rk[N], height[N];
int st[N][25], lg2[N];
struct node {
int id, x, y;
}aa[N], bb[N];
inline void buildsa() {
n = strlen(S + 1);
memset(cnt, 0, sizeof(cnt));
memset(height, 0, sizeof(height));
memset(sa, 0, sizeof(sa));
memset(rk, 0, sizeof(rk));
for(int i = 1; i <= n; i++) aa[i].id = bb[i].id = aa[i].x = aa[i].y = bb[i].x = bb[i].y = 0;
for(int i = 1; i <= n; i++) cnt[S[i]] = 1;
for(int i = 1; i <= 256; i++) cnt[i] += cnt[i - 1];
for(int i = 1; i <= n; i++) rk[i] = cnt[S[i]];
for(int L = 1; L < n; L *= 2) {
for(int i = 1; i <= n; i++) aa[i].id = i, aa[i].x = rk[i], aa[i].y = rk[i + L];
for(int i = 1; i <= n; i++) cnt[i] = 0;
for(int i = 1; i <= n; i++) cnt[aa[i].y]++;
for(int i = 1; i <= n; i++) cnt[i] += cnt[i - 1];
for(int i = n; i >= 1; i--) bb[cnt[aa[i].y]--] = aa[i];
for(int i = 1; i <= n; i++) cnt[i] = 0;
for(int i = 1; i <= n; i++) cnt[aa[i].x]++;
for(int i = 1; i <= n; i++) cnt[i] += cnt[i - 1];
for(int i = n; i >= 1; i--) aa[cnt[bb[i].x]--] = bb[i];
for(int i = 1; i <= n; i++)
if(aa[i].x == aa[i - 1].x && aa[i].y == aa[i - 1].y)
rk[aa[i].id] = rk[aa[i - 1].id];
else rk[aa[i].id] = rk[aa[i - 1].id] + 1;
} for(int i = 1; i <= n; i++) sa[rk[i]] = i; int k = 0;
for(int i = 1; i <= n; i++) {
if(k) k--;
int j = sa[rk[i] - 1];
while(i + k <= n && j + k <= n && S[i + k] == S[j + k]) k++;
height[rk[i]] = k;
}
}
inline void buildst() {
lg2[0] = -1; for(int i = 1; i < N; i++) lg2[i] = lg2[i / 2] + 1; lg2[0] = 0;
for(int i = 1; i <= n; i++) st[i][0] = height[i];
for(int j = 1; (1 << j) <= n; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++)
st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
inline int Lcp(int l, int r) {
l = rk[l], r = rk[r];
if(l > r) swap(l, r); l++;
int k = lg2[r - l + 1];
return min(st[l][k], st[r - (1 << k) + 1][k]);
}
}SA[2];
int main() {
scanf("%d", &T);
while(T--) {
scanf("%s", SA[0].S + 1);
int n = strlen(SA[0].S + 1);
for(int i = 1; i <= n; i++) a[i] = b[i] = 0;
for(int i = 1; i <= n; i++)
SA[1].S[i] = SA[0].S[n - i + 1];
SA[0].buildsa(), SA[1].buildsa();
SA[0].buildst(), SA[1].buildst();
for(int Len = 1; Len <= n / 2; Len++) {
for(int i = Len; i <= n; i += Len) {
int l = i, r = i + Len;
int L = n - (r - 1) + 1, R = n - (l - 1) + 1;
int lcp = SA[0].Lcp(l, r); lcp = min(lcp, Len);
int lcs = SA[1].Lcp(L, R); lcs = min(lcs, Len - 1);
if(lcp + lcs >= Len) {
b[i - lcs]++, b[i - lcs + (lcp + lcs - Len + 1)]--;
a[r + lcp - (lcp + lcs - Len + 1)]++, a[r + lcp]--;
}
}
} for(int i = 1; i <= n; i++) a[i] += a[i - 1], b[i] += b[i - 1];
ll ans = 0; for(int i = 1; i < n; i++) ans += a[i] * b[i + 1];
printf("%lld\n", ans);
}
return 0;
}
题解【bzoj4650 [NOI2016]优秀的拆分】的更多相关文章
- [UOJ#219][BZOJ4650][Noi2016]优秀的拆分
[UOJ#219][BZOJ4650][Noi2016]优秀的拆分 试题描述 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀 ...
- BZOJ4650 [NOI2016]优秀的拆分 【后缀数组】
题目 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆 分是优秀的.例如,对于字符串 aabaabaa,如果令 A=aabA=aa ...
- 『题解』[NOI2016]优秀的拆分
如果一个字符串可以被拆分为\(AABB\)的形式,其中$A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串\(aabaabaa\),如果令\(A=aab\),\(B=a\ ...
- UOJ#219/BZOJ4650 [NOI2016]优秀的拆分 字符串 SA ST表
原文链接http://www.cnblogs.com/zhouzhendong/p/9025092.html 题目传送门 - UOJ#219 (推荐,题面清晰) 题目传送门 - BZOJ4650 题意 ...
- BZOJ4650 NOI2016优秀的拆分(后缀数组)
显然只要求出以每个位置开始的AA串数量就可以了,将其和反串同位置的结果乘一下,加起来就是答案.考虑对每种长度的字符串计数.若当前考虑的A串长度为x,我们每隔x个字符设一个关键点,求出相邻两关键点的后缀 ...
- [BZOJ4650][NOI2016]优秀的拆分(SAM构建SA)
关于解法这个讲的很清楚了,主要用了设关键点的巧妙思想. 主要想说的是一个刚学的方法:通过后缀自动机建立后缀树,再转成后缀数组. 后缀数组功能强大,但是最令人头疼的地方是模板太难背容易写错.用这个方法, ...
- BZOJ4650: [Noi2016]优秀的拆分
考场上没秒的话多拿5分并不划算的样子. 思想其实很简单嘛. 要统计答案,求以每个位置开始和结束的AA串数量就好了.那么枚举AA中A的长度L,每L个字符设一个关键点,这样AA一定经过相邻的两个关键点.计 ...
- bzoj千题计划317:bzoj4650: [Noi2016]优秀的拆分(后缀数组+差分)
https://www.lydsy.com/JudgeOnline/problem.php?id=4650 如果能够预处理出 suf[i] 以i结尾的形式为AA的子串个数 pre[i] 以i开头的形式 ...
- BZOJ4650: [Noi2016]优秀的拆分(hash 调和级数)
题意 题目链接 Sol NOI的题都这么良心么.. 先交个\(n^4\)暴力 => 75 hash优化一下 => 90 然后\(90\)到\(100\)分之间至少差了\(10\)难度台阶= ...
随机推荐
- 实践简单的项目WC
#include<iostream> #include<fstream> #include<string> #include<Windows.h> us ...
- 跟踪分析Linux内核的启动过程--实验报告 分析 及知识重点
跟踪分析Linux内核的启动过程 攥写人:杨光 学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.stud ...
- 软件项目第一次Sprint总结
成果评分表: 组名 分数 原因 9-652 6 界面和谐生动,可运行,在目前阶段可时间基本操作 hzsy -2 代码下载,但实现安卓和相机调用 JYJe族 -1 实现安卓界面,完成一项功能,做得少 结 ...
- 基于UML的需求分析和系统设计
小序: 从学生时代就接触到UML,几年的工作中也没少使用,各种图形的概念.图形的元素和属性,以及图形的画法都不能说不熟悉.但是怎样在实际中有效地使用UML使之发挥应有的作用,怎样捕捉用户心中的需求并转 ...
- cglib 动态代理
JDK的动态代理比较慢,可以使用cglib的代理,速度比较快: package cn.demo02; import java.lang.reflect.Method; import java.util ...
- Maven入门系列(一):Eclipse中使用Maven
Maven下载和安装 在使用Maven之前首先先要下载Mavne的免安装包,下载地址:http://maven.apache.org/download.cgi 想看源码的可以下载src版本,使用的下载 ...
- docker container can not connect internet
https://stackoverflow.com/questions/23810845/i-cant-get-docker-containers-to-access-the-internet htt ...
- [微软官网]windows server 内存限制
Memory Limits for Windows and Windows Server Releases https://docs.microsoft.com/zh-cn/windows/deskt ...
- 关于linux上文件无法正确显示中文的情况解决
其实有遇到过多次,而且还有几次是css在预编译的时候,系统编码不对也会报错. 贴一个写的还不错的文章:http://www.360doc.com/content/11/0728/09/7102324_ ...
- html的表格 table
創建表格: 每一個表格以table開始: 每一個表格行以tr開始: 每一個數據以td開始:td的內容可以文本.圖像.表格.表單.段落等. 表格邊框: border設置邊框的粗細,但無法設置行間距,也無 ...