题目链接: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. JSON解析工具-org.json使用教程

    转自:http://www.open-open.com/lib/view/open1381566882614.html 一.简介  org.json是Java常用的Json解析工具,主要提供JSONO ...

  2. watch 命令

    watch是一个非常实用的命令,基本所有的Linux发行版都带有这个小工具,如同名字一样,watch可以帮你监测一个命令的运行结果,省得你一遍遍的手动运行.在Linux下,watch是周期性的执行下个 ...

  3. sublime使用技巧(2)-- 实用插件推荐【持续更新】

    1.Auto semicolon 在括号内输入分号,会自动把光标移到行尾然后再输入分号. 2.DocBlockr 补全注析格式,例如在函数上面输入/** + Enter,就会自动补全函数的注析说明. ...

  4. linux 参数内核

    优化Linux内核参数   转自:http://www.centoscn.com/CentOS/config/2013/0804/992.html vim /etc/sysctl.conf 1.net ...

  5. php 判断数组中是否有重复的值

    $input = array(4, "4", "3", 4, 3, "3"); $result = array_unique($input) ...

  6. nginx与apache的区别

    Web服务器 Web服务器也称为WWW(WORLD WIDE WEB)服务器,主要功能是提供网上信息浏览服务. 应用层使用HTTP协议. HTML文档格式. 浏览器统一资源定位器(URL). Web服 ...

  7. mysql windows安装

    http://blog.csdn.net/tossgoon/article/details/44412491 1.从该地址http://dev.mysql.com/downloads/mysql/中选 ...

  8. 谷歌浏览器console.log()失效,打印不出来内容

    这个问题困扰好几天了,网上说的都说的是下图: 勾选这三个就好了,但是我的本来就是勾选上的,还是不行. 后来发现这个: 把这个去掉就可以了,如下图: 原来是因为之前调试js的时候,使用了这个过滤,导致对 ...

  9. 关于PHP反射

    本文实例讲述了PHP反射机制原理与用法.分享给大家供大家参考,具体如下: 反射 面向对象编程中对象被赋予了自省的能力,而这个自省的过程就是反射. 反射,直观理解就是根据到达地找到出发地和来源.比如,一 ...

  10. java.lang.IllegalStateException: ImageView no longer exists. You should not use this PhotoViewAttacher any more.

    java.lang.IllegalStateException: ImageView no longer exists. You should not use this PhotoViewAttach ...