【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)
【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)
题面
题解
如果我们知道以某个位置为开始/结尾的\(AA\)串的个数
那就直接做一下乘法就好
这个怎么求?
枚举一个位置
枚举串的长度
直接暴力算就好啦
至于是否可行,用\(SA\)求\(lcp\)就好啦
这样就是\(95\)分
NOI这么好拿部分分的???
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 35000
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
int lg[MAX],n,p[20][MAX],a[MAX];
char s[MAX];
int g[MAX],f[MAX],T;
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
memset(SA,0,sizeof(SA));
memset(height,0,sizeof(height));
memset(rk,0,sizeof(rk));
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
memset(t,0,sizeof(t));
memset(a,0,sizeof(a));
}
void GetSA()
{
int m=50;
for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=n-k+1;i<=n;++i)y[++p]=i;
for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=m;++i)t[i]=0;
for(int i=1;i<=n;++i)t[x[y[i]]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;++i)
x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
for(int i=1;i<=n;++i)rk[SA[i]]=i;
for(int i=1,j=0;i<=n;++i)
{
if(j)--j;
while(a[i+j]==a[SA[rk[i]-1]+j])++j;
height[rk[i]]=j;
}
}
void Pre()
{
memset(p,63,sizeof(p));
for(int i=1;i<=n;++i)p[0][i]=height[i];
for(int j=1;j<15;++j)
for(int i=1;i<=n;++i)
p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
return Query(l,r);
}
int main()
{
for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
scanf("%d",&T);
while(T--)
{
init();
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i)a[i]=s[i]-96;
GetSA();Pre();
for(int i=1;i<=n;++i)
{
g[i]=0;
for(int l=1;l+l+i-1<=n;++l)
if(lcp(i,i+l)>=l)g[i]++;
}
for(int i=2;i<=n;++i)
{
f[i]=0;
for(int l=1;i-l-l+1>0;++l)
if(lcp(i-l-l+1,i-l+1)>=l)f[i]++;
}
int ans=0;
for(int i=1;i<n;++i)
ans+=f[i]*g[i+1];
printf("%d\n",ans);
}
return 0;
}
\(95\)分的暴力太显然了。。
原来\(NOI\)都是这样送分???
为什么NOIP 没有这么好的福利
想想怎么优化吧。。。
肯定不能枚举长度之后再暴力算每一个位置
那么,我们要考虑一个方法,
可以一次性算出连续的位置
想想我们怎么求\(AA\)这种形式??
计算\(lcp(i,i+len)>=len\)是否成立
但是,如果\(lcp(i,i+len)>=len\)
我们就会发现,有一段区间内都是有满足条件的子串
所以我们可以一起计算
现在仔细思考怎么算
因为每次是\(i\)和\(i+len\)
所以我们只要枚举位置是\(len\)的倍数的地方就好
旁边的地方我们要想办法算出来
第一个,是向后如果可以增加的话
\(lcp(i,i+len)>=L\)我就会获得向后的一段连续区间
如果只算向后,会忽略掉向前的一段
所以再算一下\(lcs(i,i+len)\)这段,这两边拼起来
如果满足条件,证明这一段区间都是可行的
这样就可以差分全部\(+1\)
如果重复的部分够多
这样算可能会影响到别的块里面
所以要强制只在自己这一段里面算
具体的实现看代码啦
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 35000
int lg[MAX],n;
char s[MAX];
int g[MAX],f[MAX],T;
struct SA
{
int p[20][MAX],a[MAX];
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
memset(SA,0,sizeof(SA));
memset(height,0,sizeof(height));
memset(rk,0,sizeof(rk));
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
memset(t,0,sizeof(t));
memset(a,0,sizeof(a));
}
void GetSA()
{
int m=50;
for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=n-k+1;i<=n;++i)y[++p]=i;
for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=m;++i)t[i]=0;
for(int i=1;i<=n;++i)t[x[y[i]]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;++i)
x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
for(int i=1;i<=n;++i)rk[SA[i]]=i;
for(int i=1,j=0;i<=n;++i)
{
if(j)--j;
while(a[i+j]==a[SA[rk[i]-1]+j])++j;
height[rk[i]]=j;
}
}
void Pre()
{
memset(p,63,sizeof(p));
for(int i=1;i<=n;++i)p[0][i]=height[i];
for(int j=1;j<15;++j)
for(int i=1;i<=n;++i)
p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
return Query(l,r);
}
}A,B;
int main()
{
for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
scanf("%d",&T);
while(T--)
{
A.init();B.init();
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;++i)A.a[i]=s[i]-96;
for(int i=1;i<=n;++i)B.a[n-i+1]=s[i]-96;
A.GetSA();A.Pre();B.GetSA();B.Pre();
for(int i=1;i<=n;++i)g[i]=f[i]=0;
for(int len=1;len<=n/2;++len)
{
for(int i=len,j=i+len;j<=n;i+=len,j+=len)
{
int x=min(A.lcp(i,j),len);
int y=min(B.lcp(n-i+2,n-j+2),len-1);
int t=x+y-len+1;
if(x+y>=len)
{
g[i-y]++;g[i-y+t]--;
f[j+x-t]++;f[j+x]--;
}
}
}
for(int i=1;i<=n;++i)g[i]+=g[i-1];
for(int i=1;i<=n;++i)f[i]+=f[i-1];
ll ans=0;
for(int i=1;i<n;++i)
ans+=1ll*f[i]*g[i+1];
printf("%lld\n",ans);
}
return 0;
}
【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)的更多相关文章
- [NOI2016]优秀的拆分 后缀数组
题面:洛谷 题解: 因为对于原串的每个长度不一定等于len的拆分而言,如果合法,它将只会被对应的子串统计贡献. 所以子串这个限制相当于是没有的. 所以我们只需要对于每个位置i求出f[i]表示以i为开头 ...
- BZOJ.4650.[NOI2016]优秀的拆分(后缀数组 思路)
BZOJ 洛谷 令\(st[i]\)表示以\(i\)为开头有多少个\(AA\)这样的子串,\(ed[i]\)表示以\(i\)结尾有多少个\(AA\)这样的子串.那么\(Ans=\sum_{i=1}^{ ...
- UOJ #219 BZOJ 4650 luogu P1117 [NOI2016]优秀的拆分 (后缀数组、ST表)
连NOI Day1T1都不会做...看了题解都写不出来还要抄Claris的代码.. 题目链接: (luogu)https://www.luogu.org/problemnew/show/P1117 ( ...
- BZOJ 4650 [Noi2016]优秀的拆分 ——后缀数组
我们只需要统计在某一个点开始的形如$AA$字符串个数,和结束的个数相乘求和. 首先枚举循环节的长度L.即$\mid (A) \mid=L$ 然后肯定会经过s[i]和[i+L]至少两个点. 然后我们可以 ...
- [UOJ#219][BZOJ4650][Noi2016]优秀的拆分
[UOJ#219][BZOJ4650][Noi2016]优秀的拆分 试题描述 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 A 和 B 是任意非空字符串,则我们称该字符串的这种拆分是优秀 ...
- [NOI2016]优秀的拆分(SA数组)
[NOI2016]优秀的拆分 题目描述 如果一个字符串可以被拆分为 \(AABB\) 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 \(aabaaba ...
- BZOJ4650 [NOI2016]优秀的拆分 【后缀数组】
题目 如果一个字符串可以被拆分为 AABBAABB 的形式,其中 AA 和 BB 是任意非空字符串,则我们称该字符串的这种拆 分是优秀的.例如,对于字符串 aabaabaa,如果令 A=aabA=aa ...
- bzoj千题计划317:bzoj4650: [Noi2016]优秀的拆分(后缀数组+差分)
https://www.lydsy.com/JudgeOnline/problem.php?id=4650 如果能够预处理出 suf[i] 以i结尾的形式为AA的子串个数 pre[i] 以i开头的形式 ...
- UOJ#219. 【NOI2016】优秀的拆分 [后缀数组 ST表]
#219. [NOI2016]优秀的拆分 题意:求有多少AABB样子的子串,拆分不同的同一个子串算多个 一开始一直想直接求,并不方便 然后看了一眼Claris的题解的第一行就有思路了 如果分开,求\( ...
- UOJ#219/BZOJ4650 [NOI2016]优秀的拆分 字符串 SA ST表
原文链接http://www.cnblogs.com/zhouzhendong/p/9025092.html 题目传送门 - UOJ#219 (推荐,题面清晰) 题目传送门 - BZOJ4650 题意 ...
随机推荐
- UbuntuNFS服务器配置
NFS服务的配置====================1,下载并安装NFS sudo apt-get install nfs-kernel-server 2,配置NFS sudo vi /etc/e ...
- 练手项目:利用pygame库编写射击游戏
本项目使用pygame模块编写了射击游戏,目的在于训练自己的Python基本功.了解中小型程序框架以及学习代码重构等.游戏具有一定的可玩性,感兴趣的可以试一下. 项目说明:出自<Python编程 ...
- cnblogs的使用
cnblogs的使用 选择使用cnblogs而不是csdn,答案是很明显的.csdn每次创建博客之后会有一段时间的审核期,这大大的影响了用户体验.此外,cnblogs的用户群以及使用模式有着很大的诱惑 ...
- angularjs 指令详解
一.指令定义 对于指令,可以把它简单的理解成在特定DOM元素上运行的函数,指令可以扩展这个元素的功能. 首先来看个完整的参数示例再来详细的介绍各个参数的作用及用法: <div my-direct ...
- Django——ContentType及ContentType-signals的使用
一.ContentType 在django中,有一个记录了项目中所有model元数据的表,就是ContentType,表中一条记录对应着一个存在的model,所以可以通过一个ContentType表的 ...
- php会话(session)实现原理
先考虑如下问题: 禁用了cookie,session能否使用? 如何把session存储数据库/memcached/redis 如何实现一个严格的30分钟过期的会话?. 如何实践web服务器集群的会话 ...
- python进阶学习笔记(一)
python进阶部分要学习的内容: 学习目标: 1.函数式编程 1.1,什么是函数式编程 函数式编程是一种抽象计算的编程模式 不同语言的抽象层次不同: 函数式编程的特点: python支持的函数式编程 ...
- STAThread 和 MTAThread
STAThread:single threaded apartment 直译过来是:单线程单元套间 MTAThread:multiple threaded apartment 直译过来是:多线程单元套 ...
- hibernate的事务管理和session对象的详解
//开启事务 transaction=session.beginTransaction(); 上面为开启事务 transaction.commit();这个为提交事务 从session对象中获取事务后 ...
- (MonoGame从入门到放弃-1) MonoGame环境搭建
MonoGame在国内的市场应该比较小吧,工作之余想学习一下游戏开发,期间也尝试过多款游戏引擎,如 Cocos2dx Egret layabox之类的,这几个目前主推的都是Js或者ts作为开发语言. ...