题解-NOI2016 优秀的拆分
\(T\) 组测试数据。求字符串 \(s\) 的所有子串拆成 \(AABB\) 形式的方案总和。
数据范围:\(1\le T\le 10\),\(1\le n\le 3\cdot 10^4\)。
这道题太神了,能一次做出这题的人往往是人形自走题库。真的全是套路!
令 \(n=|s|\),\(f_i\) 表示有几个以 \(s_i\) 结尾的 \(AA\) 串,\(g_i\) 表示有几个以 \(s_i\) 开头的 \(BB\) 串。
\]
然后只需求 \(f_i,g_i\)。
套路地为 \(s\) 以及 \(s\) 的反串建后缀数组并为 \(height\) 数组装上 \(st\) 表。
于是就可以 \(\Theta(1)\) 求任意两个前缀的最长公共后缀和任意两个后缀的最长公共前缀了。
这时候求 \(f_i\) 的 \(\Theta(n^2)\) 的做法(\(g_i\) 同):
枚举 \(A\) 串长度 \(w\),然后枚举 \(s\) 中的下标 \(l,r(r-l=w)\)。
如果两个后缀的最长公共前缀\(\ge w\),则 \(f_{r+w-1}++\)。
如下图,三条黄线间的两个串组成 \(AA\) 串。

考虑一种套路优化(\(g_i\) 同):
枚举了 \(w\) 以后任何相距 \(w\) 的下标是对应的。
所以可以在 \(w\) 倍数的下标设断点,此时一个 \(A\) 串至少要经过 \(1\) 个断点。
枚举相邻两个断点 \(l\) 和 \(r\),求出它们的前缀最长公共后缀长度 \(lcs\) 和后缀最长公共前缀长度 \(lcp\) 之和 \(len\):
表示经过两个断点且开头距离 \(w\) 的最长公共子串。
如果 \(len<w\),说明没有同时经过这两个断点的 \(AA\) 串。
否则,\(f_{r+lcp-(lcp+lcs-1)\to r+lcp-1}++\)。
如下图,即两条紫线以及之间的子串为 \(AA\) 串:

\(f_i,g_i\) 需要区间加,可以差分处理。
时间复杂度 \(\Theta(T\cdot n\log n)\)。
小蒟蒻讲不清楚,放代码吧:
#include <bits/stdc++.h>
using namespace std;
//Start
typedef long long ll;
typedef double db;
#define mp(a,b) make_pair(a,b)
#define x first
#define y second
#define b(a) a.begin()
#define e(a) a.end()
#define sz(a) int((a).size())
#define pb(a) push_back(a)
const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;
//Data
const int N=3e4;
int n,f[N+7],g[N+7];
//SuffixArray
struct SuffixArray{
char s[N+7];
int m,c[N+7],tp[N+7],rk[N+7],sa[N+7];
int h[N+7],st[N+7][20];
void csort(){
for(int i=0;i<=m;i++) c[i]=0;
for(int i=1;i<=n;i++) c[rk[i]]++;
for(int i=1;i<=m;i++) c[i]+=c[i-1];
for(int i=n;i>=1;i--) sa[c[rk[tp[i]]]--]=tp[i];
}
void build(){
memset(c,0,sizeof c);
memset(tp,0,sizeof tp);
memset(rk,0,sizeof rk);
memset(sa,0,sizeof sa);
memset(h,0,sizeof h);
memset(st,0,sizeof st);
for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
m=128,csort();
for(int w=1,p=1,i;p<n;w<<=1,m=p){
for(p=0,i=n-w+1;i<=n;i++) tp[++p]=i;
for(i=1;i<=n;i++)if(sa[i]>w) tp[++p]=sa[i]-w;
csort(),swap(rk,tp),rk[sa[1]]=p=1;
for(i=2;i<=n;rk[sa[i]]=p,i++)
if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+w]!=tp[sa[i-1]+w]) p++;
}
for(int i=1,j,k=0;i<=n;h[rk[i++]]=k)
for(k=k?k-1:k,j=sa[rk[i]-1];s[i+k]==s[j+k];k++);
for(int i=1;i<=n;i++) st[i][0]=h[i];
for(int w=1;w<=18;w++)
for(int i=1;i+(1<<w)-1<=n;i++)
st[i][w]=min(st[i][w-1],st[i+(1<<(w-1))][w-1]);
}
int Lcp(int a,int b){
int l=rk[a],r=rk[b];
if(l>r) swap(l,r); l++;
int k=log2(r-l+1);
return min(st[l][k],st[r-(1<<k)+1][k]);
}
}a,b;
//KonnyWen
void KonnyWen(){
memset(f,0,sizeof f);
memset(g,0,sizeof g);
scanf("%s",&a.s[1]),n=strlen(&a.s[1]);
for(int i=1;i<=n;i++) b.s[i]=a.s[n+1-i];
a.build(),b.build();
for(int i=1;i<=n;i++) f[i]=g[i]=0;
for(int w=1;w<=(n>>1);w++)
for(int i=w;i<=n;i+=w){
int l=i,r=i+w;
int lcp=min(w,a.Lcp(l,r));
int lcs=min(w-1,b.Lcp(n-(l-1)+1,n-(r-1)+1));
if(lcp+lcs>=w){
int cov=lcp+lcs-w+1;
f[r+lcp-cov]++,f[r+lcp]--;
g[l-lcs]++,g[l-lcs+cov]--;
}
}
for(int i=1;i<=n;i++) f[i]+=f[i-1],g[i]+=g[i-1];
ll ans=0;
for(int i=1;i<n;i++) ans+=f[i]*g[i+1];
printf("%lld\n",ans);
}
//Main
int main(){
int t; scanf("%d",&t);
while(t--) KonnyWen();
return 0;
}
祝大家学习愉快!
题解-NOI2016 优秀的拆分的更多相关文章
- [NOI2016]优秀的拆分&&BZOJ2119股市的预测
[NOI2016]优秀的拆分 https://www.lydsy.com/JudgeOnline/problem.php?id=4650 题解 如果我们能够统计出一个数组a,一个数组b,a[i]表示以 ...
- 【BZOJ4560】[NOI2016]优秀的拆分
[BZOJ4560][NOI2016]优秀的拆分 题面 bzoj 洛谷 题解 考虑一个形如\(AABB\)的串是由两个形如\(AA\)的串拼起来的 那么我们设 \(f[i]\):以位置\(i\)为结尾 ...
- [UOJ#219][BZOJ4650][Noi2016]优秀的拆分
[UOJ#219][BZOJ4650][Noi2016]优秀的拆分 试题描述 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀 ...
- [NOI2016]优秀的拆分(SA数组)
[NOI2016]优秀的拆分 题目描述 如果一个字符串可以被拆分为 \(AABB\) 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 \(aabaaba ...
- luogu1117 [NOI2016]优秀的拆分
luogu1117 [NOI2016]优秀的拆分 https://www.luogu.org/problemnew/show/P1117 后缀数组我忘了. 此题哈希可解决95分(= =) 设\(l_i ...
- BZOJ4650:[NOI2016]优秀的拆分——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=4650 https://www.luogu.org/problemnew/show/P1117 如果 ...
- 『题解』[NOI2016]优秀的拆分
如果一个字符串可以被拆分为\(AABB\)的形式,其中$A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串\(aabaabaa\),如果令\(A=aab\),\(B=a\ ...
- [NOI2016]优秀的拆分 后缀数组
题面:洛谷 题解: 因为对于原串的每个长度不一定等于len的拆分而言,如果合法,它将只会被对应的子串统计贡献. 所以子串这个限制相当于是没有的. 所以我们只需要对于每个位置i求出f[i]表示以i为开头 ...
- P1117 [NOI2016]优秀的拆分
$ \color{#0066ff}{ 题目描述 }$ 如果一个字符串可以被拆分为\(AABB\)的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串\(aab ...
随机推荐
- 基于FFmpeg的Dxva2硬解码及Direct3D显示(一)
目录 前言 名词解释 代码实现逻辑 前言 关于视频软解码的资料网上比较多了,但是关于硬解可供参考的资料非常之有限,虽然总得来说软解和硬解的基本逻辑一样,但是实现细节上的差别还是比较多的.虽然目前功能已 ...
- App与小程序对接
背景: 商品详情页,点击分享,分享到微信好友,点开链接App拉起小程序. 用户在小程序浏览完成,跳转至原App购买商品. 功能点: 实现APP与小程序互调. 前提: 已对接好友盟ShareSDK(需要 ...
- 《HarmonyOS设备开发入门手册》
HarmonyOS设备开发入门手册-更新 [欢迎大家点击下载] 作者:连志安 想了解更多内容,请访问: 51CTO和华为官方战略合作共建的鸿蒙技术社区 https://harmonyos.51cto. ...
- error while loading shared libraries解決方法
在linux下运行程序时,发现了error while loading shared libraries这种错误,一时间不知道解决办法,在网上搜索,终于解决了. error while loading ...
- yum 常用命令使用
1.向服务器上传文件或者下载文件 我们知道我们经常需要向服务器上传文件,或者从服务器下载文件,rz和sz命令可以满足我们的要求, 只不过默认情况下是不能使用的.我们需要使用yum install lr ...
- js常用通用函数
//判断是否数字 function isNumber(str){ var n = Number(str); if (!isNaN(n)){ return true; }else{ return fal ...
- 如何调整MathType公式的字体大小
作为一名理科生,想必大家都在为编辑公式而烦恼,在Word中要想完美插入公式,还真不是那么简单的.首先要使用专业的公式编辑器MathType,其次还要对公式的大小进行修改,这样才能看起来是相融合的文章. ...
- FL Studio乐理教程之调式音阶
在我们使用FL制作音乐时,乐理是必不可少的制作基础,本篇教程将结合FL Studio为大家讲解基础乐理及在FL Studio20中的使用技巧. 添加一个乐器,打开Piano Roll(钢琴窗). 首先 ...
- FL Studio 插件使用技巧——Fruity Reeverb 2 (上)
许多学习FL的用户会发现,自己在听大师的电子音乐作品时都能感受到他们的音乐有一股强大的空间感,有时还能感知到深邃的意境.不少人会因此而疑惑:为什么出自我们之手的音乐就没有这样的效果呢?我们的音乐里到底 ...
- 【VUE】7.Vuex基本使用
1. 安装Vuex npm install vuex --save 2. 导入Vuex包 import Vuex from 'vuex' Vue.use(Vuex) 3. 创建store对象 cons ...