题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4650

题意:

  给你一个字符串s,问你s及其子串中,将它们拆分成"AABB"的方式共有多少种。

题解:

  先只考虑"AA"的形式。

  设pre[i]表示以s[i]结尾的"AA"串共有多少个,nex[i]表示以s[i]开头的"AA"串共有多少个。

  那么拆分成"AABB"的总方案数 = ∑ pre[i]*nex[i+1]。

  然后考虑如何求pre和nex数组。

  对于"AA"形式的串,有一个性质:

    假设一个"AA"串的长度为len。

    如果在s串上每隔len的距离设置一个关键点,那么这个"AA"串一定经过相邻的两个关键点。

  所以先枚举"AA"串的长度len,然后枚举是哪两个相邻的关键点。

  设当前的两个相邻关键点分别为:x = i*len, y = (i+1)*len

  设前缀s[1 to x]和前缀s[1 to y]的LCS为a,后缀s[x to n]和后缀s[y to n]的LCP为b

  令tt = a+b-1

  如果有tt >= len,则就找到了a+b-len个长度为len的"AA"串。

  又因为这些"AA"串是必须经过x,y这两个关键点的(否则会重复统计)

  所以上面的a = min(a,len), b = min(b,len)。

  找到的这些"AA"串会对pre数组和nex数组产生贡献。

  所有这些"AA"串的开头为区间[x-a+1, x+b-len],结尾为区间[y-a+len, y+b-1]。

  所以要给nex[x-a+1 to x+b-len]加1,给pre[y-a+len to y+b-1]加1

  差分一下,最后求一遍前缀个就好。

  这样pre和nex就求好了,然后统计"AABB"的总数即可。

  O(nlogn)求后缀数组

  O(nlogn)求pre和nex(调和级数复杂度)

  O(n)统计"AABB"

  总复杂度O(nlogn)

AC Code:

 #include <iostream>
#include <stdio.h>
#include <string.h>
#define MAX_N 30005
#define MAX_K 20 using namespace std; struct SA
{
int n;
int a[MAX_N];
int sa[MAX_N];
int rk[MAX_N];
int t1[MAX_N];
int t2[MAX_N];
int cnt[MAX_N];
int tsa[MAX_N];
int height[MAX_N];
char s[MAX_N]; int lg[MAX_N];
int dat[MAX_N][MAX_K]; void rsort()
{
memset(cnt,,sizeof(cnt));
for(int i=;i<=n;i++) cnt[t2[i]]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=n;i>=;i--) tsa[cnt[t2[i]]--]=i;
memset(cnt,,sizeof(cnt));
for(int i=;i<=n;i++) cnt[t1[i]]++;
for(int i=;i<=n;i++) cnt[i]+=cnt[i-];
for(int i=n;i>=;i--) sa[cnt[t1[tsa[i]]]--]=tsa[i];
} void suffix()
{
memset(cnt,,sizeof(cnt));
for(int i=;i<=n;i++) a[i]=s[i],cnt[a[i]]++;
for(int i='a';i<='z';i++) cnt[i]+=cnt[i-];
for(int i=;i<=n;i++) rk[i]=cnt[a[i]];
int len=;
while(len<n)
{
for(int i=;i<=n;i++)
{
t1[i]=rk[i];
t2[i]=i+len<=n ? rk[i+len] : ;
}
rsort();
for(int i=;i<=n;i++)
{
rk[sa[i]]=rk[sa[i-]]+(t1[sa[i]]!=t1[sa[i-]] || t2[sa[i]]!=t2[sa[i-]]);
}
len<<=;
}
int k=;
for(int i=;i<=n;i++)
{
k=k?k-:k;
int j=sa[rk[i]-];
while(a[i+k]==a[j+k]) k++;
height[rk[i]]=k;
}
} void init_st()
{
lg[]=-;
for(int i=;i<=n;i++)
{
lg[i]=lg[i>>]+;
dat[i][]=height[i];
}
for(int k=;(<<k)<=n;k++)
{
for(int i=;i+(<<k)-<=n;i++)
{
dat[i][k]=min(dat[i][k-],dat[i+(<<(k-))][k-]);
}
}
} int lcp(int l,int r)
{
l=rk[l]; r=rk[r];
if(l>r) swap(l,r); l++;
int k=lg[r-l+];
return min(dat[l][k],dat[r-(<<k)+][k]);
} void init(char *_s,int _n)
{
memset(a,,sizeof(a));
memset(sa,,sizeof(sa));
memset(rk,,sizeof(rk));
memset(tsa,,sizeof(tsa));
memset(height,,sizeof(height));
n=_n;
for(int i=;i<=n;i++) s[i]=_s[i];
suffix();
init_st();
}
}; int n,t;
char str[MAX_N];
char rev[MAX_N];
long long ans;
long long pre[MAX_N];
long long nex[MAX_N];
SA sa1,sa2; inline void add(long long *a,int l,int r)
{
a[l]++; a[r+]--;
} void cal_aa()
{
memset(pre,,sizeof(pre));
memset(nex,,sizeof(nex));
for(int len=;len<=n/;len++)
{
for(int i=;(i+)*len<=n;i++)
{
int x=i*len,y=(i+)*len;
int a=sa2.lcp(n-x+,n-y+),b=sa1.lcp(x,y);
a=min(a,len); b=min(b,len);
int tt=a+b-;
if(tt>=len)
{
add(nex,x-a+,x+b-len);
add(pre,y-a+len,y+b-);
}
}
}
for(int i=;i<=n;i++)
{
pre[i]+=pre[i-];
nex[i]+=nex[i-];
}
} void cal_aabb()
{
ans=;
for(int i=;i<n;i++)
{
ans+=pre[i]*nex[i+];
}
} int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%s",str+);
n=strlen(str+);
for(int i=,j=n;i<=n;i++,j--) rev[j]=str[i];
sa1.init(str,n); sa2.init(rev,n);
cal_aa();
cal_aabb();
printf("%lld\n",ans);
}
}

BZOJ 4650 [Noi2016]优秀的拆分:后缀数组的更多相关文章

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

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

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

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

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

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

  4. [BZOJ]4650: [Noi2016]优秀的拆分

    Time Limit: 30 Sec  Memory Limit: 512 MB Description 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串, ...

  5. 【刷题】BZOJ 4650 [Noi2016]优秀的拆分

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

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

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

  7. [NOI2016]优秀的拆分(SA数组)

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

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

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

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

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

随机推荐

  1. CAP理论学习

    CAP理论是对分布式系统的3个特性所下的一个定性的结论,可用于指导分布式系统的设计. CAP理论断言任何基于网络的数据共享系统,最多只能满足数据一致性.可用性.分区容忍性三要素中的两个要素. 在英语中 ...

  2. u-boot-2014_04在TQ2440上的移植

    本文详细介绍了新版本的u-boot-2014_04在tq2440平台上的移植过程,期间参考了网上的其他移植文档,还有韦东山的移植uboot视频,讲的很好.下面是共享链接,欢迎下载,一同学习.其中有移植 ...

  3. prometheus监控方案

    简介 prometheus 是一个开源的系统监控和告警的工具包,其采用pull方式采集时间序列,通过http协议传输. 架构 每个应用都通过javaagent向外提供一个http服务暴露出自己的JMX ...

  4. Java线程—-Runnable和Callable的区别和联系

    Java 提供了三种创建线程的方法 1.继承Thread接口 public class Thread2Thread { public static void main(String[] args) { ...

  5. COM线程单元

    节选自C#高级编程 不管是单线程单元还是多线程单元,一个线程只能属于一个单元. 1) 单线程单元(apartment, 寓所,套间) 单线程单元与它拥有的线程是一对一的关系.COM对象在编写时不是线程 ...

  6. jmeter java 请求 payload

    1.注册页面抓包看见内容如下: POST http://test.nnzhp.cn/bbs/forum.php?mod=post&action=edit&extra=&edit ...

  7. Pycharm context menu disable RUN option

    这个问题很坑.正常来说一个文件右键出来的是 Run 选项, 可是近期几个文件都是 Unititest 的測试选项,每次要执行的时候都要手工去配置Run Option,在尝试了: 0. 重置IDE配置 ...

  8. 【BZOJ3689】异或之 堆+可持久化Trie树

    [BZOJ3689]异或之 Description 给定n个非负整数A[1], A[2], ……, A[n].对于每对(i, j)满足1 <= i < j <= n,得到一个新的数A ...

  9. 摘要: CentOS 6.5搭建Redis3.2.8伪分布式集群

    from https://my.oschina.net/ososchina/blog/856678     摘要: CentOS 6.5搭建Redis3.2.8伪分布式集群 前言 最近在服务器上搭建了 ...

  10. 九度OJ 1344:可乐瓶展览 (DP)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:430 解决:76 题目描述: 众所周知JOBDU旗下的JOBCOLA公司是文明全球的著名可乐制造商,与其它可乐公司不同的是,JOBCOLA可 ...