[NOI2016]优秀的拆分

题目描述

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

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

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

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

以下事项需要注意:

出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。

在一个拆分中,允许出现 \(A=B\) 。例如 \(cccc\) 存在拆分 \(A=B=c\) 。

字符串本身也是它的一个子串。

输入输出格式

输入格式:

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

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

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

输出格式:

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

输入输出样例

输入样例#1:

复制

4

aabbbb

cccccc

aabaabaabaa

bbaabaababaaba

输出样例#1:

复制

3

5

4

7

说明

我们用 \(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\) 的长度,每个测试点的详细数据范围见下表:

题解

先推荐一下这一个博客,可以说是写的非常清楚了

好了,现在我来说一说自己的理解。

先写一手hash吧,95分,可以说是非常良心了,我们只需要预先处理好这个字符串的hash值,然后一个循环枚举起点,一个循环枚举长度,然后记录一下AA(注意BB就不用再管了)的\(l[i]\)和\(r[i]\)相乘即可。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2001;
const ll mod=(1<<31)-1;
const int base=131;
ll addx[N],hah[N],n,T;
char s[N+1];
void solve(){
ll ans=0;
scanf("%s",s+1);
n=strlen(s+1);
addx[0]=1;
for(int i=1;i<=n;i++)addx[i]=addx[i-1]*base%mod;
for(int i=1;i<=n;i++)hah[i]=(hah[i-1]*base+s[i])%mod;
for(int i=1;i<=n;i++)
{
ll l=0,r=0;
for(int j=1;j<=n;j++){
if(i-2*j>=0){
ll x=((hah[i]-hah[i-j]*addx[j])%mod+mod)%mod;
ll y=((hah[i-j]-hah[i-j*2]*addx[j])%mod+mod)%mod;
if(x==y)l++;
}
if(i+2*j<=n){
ll x=((hah[i+j]-hah[i]*addx[j])%mod+mod)%mod;
ll y=((hah[i+2*j]-hah[i+j]*addx[j])%mod+mod)%mod;
if(x==y)r++;
}
}
ans+=l*r;
}
printf("%lld\n",ans);
} int main(){
cin>>T;
while(T--)
solve();
return 0;
}

好了,接下来讲一讲满分做法。

推了我蛮久(最后还是翻题解了)

AA相同,即有子串是相同且靠近的,这让我们想到什么?

后缀数组维护\(lcp\)

那么一个简单的结论变出来了。

每次枚举长度\(len\) ,\(j-i=len\)后

AA串一共有\(lcp-len+1\)个。

这个比较好手推。

枚举长度\(1-n\),在枚举起点和终点每次跳\(len\)。

然后时间复杂度是调和级数的。即\(O(nlogn)\)

但是实际上我们是有遗漏的情况的。

因为起点和终点是每次跳\(len\)的,且只求的后缀的部分。

求图证翻上面的博客。这道题我承认有点讲不清。

那么如果有以起点为末尾的前缀呢与终点匹配呢?

那么我们也要求出\(lcs\)即最长公共后缀。

AA的贡献为\(lcs+lcp>=len\)

但是这样又又又有问题了

我们会重复计算别的子串。

怎么办?我们限制长度。就是说,这个区间算过就直接跳过。

换句话说,就是超出本次枚举长度以外的贡献忽略??

我感觉我有点讲不清了qwq,还是95分好拿,何必zuosi去AC呢。

ps:最后一个点还要开long long


代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#define ll long long
using namespace std;
const int N=30001;
int T,n,m;
ll l[N],r[N],lg[N];
char ss[N];
struct node{
int st[21][N];
char s[N];
int tp[N],rak[N],sa[N],num[N],H[N];
void clear(){
memset(num,0,sizeof(num));
memset(rak,0,sizeof(rak));
memset(H,0,sizeof(H));
memset(sa,0,sizeof(sa));
memset(tp,0,sizeof(tp));
}
void Sort(){
for(int i=0;i<=m;i++)num[i]=0;
for(int i=1;i<=n;i++)num[rak[i]]++;
for(int i=1;i<=m;i++)num[i]+=num[i-1];
for(int i=n;i>=1;i--)sa[num[rak[tp[i]]]--]=tp[i];
}
void SA_sort(){
m=10001;
for(int i=1;i<=n;i++)tp[i]=i,rak[i]=s[i]-'a'+1;Sort();
int cnt=0,p=0,w=1;
while(p<n){
for(int i=1;i<=w;i++)tp[++cnt]=n-w+i;
for(int i=1;i<=n;i++)if(sa[i]>w)tp[++cnt]=sa[i]-w;
Sort();swap(tp,rak);rak[sa[1]]=p=1;
for(int i=2;i<=n;i++)
if(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w])
rak[sa[i]]=p;else rak[sa[i]]=++p;
w<<=1;m=p;cnt=0;
}
for(int i=1;i<=n;i++)rak[sa[i]]=i;
int k=0;
for(int i=1;i<=n;i++){
if(k)k--;
while(s[i+k]==s[sa[rak[i]-1]+k]&&i+k<=n&&sa[rak[i]-1]+k<=n)k++;
H[rak[i]]=k;
}
memset(st,63,sizeof(st));
for(int i=1;i<=n;i++)
st[0][i]=H[i];
for(int j=1;j<=15;j++)
for(int i=1;i<=n;i++)
st[j][i]=min(st[j-1][i],st[j-1][i+(1<<(j-1))]);
}
int query(int i,int j){
int l=rak[i],r=rak[j];if(l>r)swap(l,r);l++;
return min(st[lg[r-l+1]][l],st[lg[r-l+1]][r-(1<<lg[r-l+1])+1]);
}
}s1,s2; void solve(){
for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
s1.clear();s2.clear();
scanf("%s",ss+1);
n=strlen(ss+1);
for(int i=1;i<=n;i++)s1.s[i]=ss[i];
for(int i=1;i<=n;i++)s2.s[n-i+1]=ss[i];
s1.SA_sort();
s2.SA_sort();
for(int i=1;i<=n;i++)l[i]=r[i]=0;
for(int k=1;k<=n/2;k++)
for(int i=k,j=i+k;j<=n;j+=k,i+=k){
int x=min(k,s1.query(i,j));
int y=min(k-1,s2.query(n-i+2,n-j+2));
int en=x+y-k+1;
if(x+y>=k){
r[i-y]++;r[i-y+en]--;
l[j+x-en]++;l[j+x]--;
}
}
for(int i=1;i<=n;i++)
l[i]+=l[i-1],r[i]+=r[i-1];
ll ans=0;
for(int i=1;i<n;i++)
ans+=1ll*l[i]*r[i+1];
printf("%lld\n",ans);
} int main(){
cin>>T;
while(T--)
solve();
return 0;
}

[NOI2016]优秀的拆分(SA数组)的更多相关文章

  1. [NOI2016]优秀的拆分 后缀数组

    题面:洛谷 题解: 因为对于原串的每个长度不一定等于len的拆分而言,如果合法,它将只会被对应的子串统计贡献. 所以子串这个限制相当于是没有的. 所以我们只需要对于每个位置i求出f[i]表示以i为开头 ...

  2. UOJ #219 BZOJ 4650 luogu P1117 [NOI2016]优秀的拆分 (后缀数组、ST表)

    连NOI Day1T1都不会做...看了题解都写不出来还要抄Claris的代码.. 题目链接: (luogu)https://www.luogu.org/problemnew/show/P1117 ( ...

  3. BZOJ.4650.[NOI2016]优秀的拆分(后缀数组 思路)

    BZOJ 洛谷 令\(st[i]\)表示以\(i\)为开头有多少个\(AA\)这样的子串,\(ed[i]\)表示以\(i\)结尾有多少个\(AA\)这样的子串.那么\(Ans=\sum_{i=1}^{ ...

  4. BZOJ 4650 [Noi2016]优秀的拆分 ——后缀数组

    我们只需要统计在某一个点开始的形如$AA$字符串个数,和结束的个数相乘求和. 首先枚举循环节的长度L.即$\mid (A) \mid=L$ 然后肯定会经过s[i]和[i+L]至少两个点. 然后我们可以 ...

  5. [NOI2016]优秀的拆分&&BZOJ2119股市的预测

    [NOI2016]优秀的拆分 https://www.lydsy.com/JudgeOnline/problem.php?id=4650 题解 如果我们能够统计出一个数组a,一个数组b,a[i]表示以 ...

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

    [UOJ#219][BZOJ4650][Noi2016]优秀的拆分 试题描述 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀 ...

  7. 题解-NOI2016 优秀的拆分

    NOI2016 优秀的拆分 \(T\) 组测试数据.求字符串 \(s\) 的所有子串拆成 \(AABB\) 形式的方案总和. 数据范围:\(1\le T\le 10\),\(1\le n\le 3\c ...

  8. luogu1117 [NOI2016]优秀的拆分

    luogu1117 [NOI2016]优秀的拆分 https://www.luogu.org/problemnew/show/P1117 后缀数组我忘了. 此题哈希可解决95分(= =) 设\(l_i ...

  9. 【BZOJ4560】[NOI2016]优秀的拆分

    [BZOJ4560][NOI2016]优秀的拆分 题面 bzoj 洛谷 题解 考虑一个形如\(AABB\)的串是由两个形如\(AA\)的串拼起来的 那么我们设 \(f[i]\):以位置\(i\)为结尾 ...

随机推荐

  1. Android 度量单位

    单位 注释 px(像素) 每个像素对应手机上的一个点,在不同设备上1px表示的长度不一定相同 screen size(屏幕尺寸) 指手机对角线的长度,如4.7英寸 resolution(分辨率) 指屏 ...

  2. Linux部署之批量自动安装系统之Kickstart篇

    1.         安装   2.         在桌面环境下啊配置   3.         Kickstart之基本配置   4.         Kickstart之安装方法   5.    ...

  3. hdu 1051 - 贪心,水题

    题目链接 一堆小木棍,每个有两个属性值(l,w),对小木棍分组,每一组内的小木棍存在这样一个序列满足s1<=s2<=s3.....<=sn,[s1<=s2当且仅当s1.l< ...

  4. sql中对日期的筛选

    #几个小时内的数据 DATE_SUB(NOW(), INTERVAL 5 HOUR) #今天 select * from 表名 where to_days(时间字段名) = to_days(now() ...

  5. Qt5的插件机制(1)--Qt 框架中的插件载入机制概述

    概述 Qt的源代码中通过 Q<pluginType>Factory.Q<pluginType>Plugin 和 Q<pluginType> 这三个类实现了Qt的插件 ...

  6. spfile

    1 让ORACLE自己主动从spfile启动  SQL> create spfile='/dev/vx/rdsk/vgora/lv_spfile' from pfile;  SQL> sh ...

  7. bzoj1800: [Ahoi2009]fly 飞行棋(乱搞)

    1800: [Ahoi2009]fly 飞行棋 题目:传送门 题解: 大水题,早上签个到 没什么好说的...搞个前缀和,算个周长... 周长为奇数肯定误解啊废话QWQ 那么看到n<=20,还不暴 ...

  8. struts2入门(搭建环境、配置、示例)

    转自:https://blog.csdn.net/u012862311/article/details/53412716 1.下载Struts2的jar包 下载地址:http://archive.ap ...

  9. rest_framework 权限功能

    权限: 问题:不用视图不用权限可以访问 基本使用 写上一个权限类 创建utils 中 permission.py文件 class SvipPermisson(object): message = &q ...

  10. A string is a sequence

    A string is a sequence of characters. You can access the characters one at a time with the bracket o ...