【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

题面

BZOJ

Uoj

题解

如果我们知道以某个位置为开始/结尾的\(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】优秀的拆分(后缀数组)的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

  7. BZOJ4650 [NOI2016]优秀的拆分 【后缀数组】

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

  8. bzoj千题计划317:bzoj4650: [Noi2016]优秀的拆分(后缀数组+差分)

    https://www.lydsy.com/JudgeOnline/problem.php?id=4650 如果能够预处理出 suf[i] 以i结尾的形式为AA的子串个数 pre[i] 以i开头的形式 ...

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

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

  10. UOJ#219/BZOJ4650 [NOI2016]优秀的拆分 字符串 SA ST表

    原文链接http://www.cnblogs.com/zhouzhendong/p/9025092.html 题目传送门 - UOJ#219 (推荐,题面清晰) 题目传送门 - BZOJ4650 题意 ...

随机推荐

  1. ubunt tftp服务器搭建

    默认安装的Ubuntu系统没有包含TFTP的服务端和客户端,可以通过命令行来下载安装,步骤如下: (1)安装客户端. root@ www.linuxidc.com:~# apt-get install ...

  2. CSS小技巧使用

    1.清除浮动 浮动给我们的代码带来的麻烦,想必不需要多说,我们会用很多方式来避免这种麻烦,其中我觉得最方便也是兼容性最好的一种是....// 清除浮动 .clearfix{ zoom: 1; } .c ...

  3. ajax同步与异步的坑

      之前工作中一个需求,需要动态的添加一组下拉菜单并为这个菜单绑定一个插件,很明显获取数据用Ajax,这本身是没错的,坑就坑在我用了 同步请求,当服务器端正确返回数据时再去执行下一个方法,这逻辑本身没 ...

  4. angularjs 服务详解

    一.服务 服务提供了一种能在应用的整改生命周期内保持数据的方法,它能够在控制器之间进行通信,并保持数据的一致性. 1.服务是一个单例对象,在每个应用中只会被实例化一次(被$injector): 2.服 ...

  5. 工作笔记--自动切换host的python code

    修改host代码: #coding:utf-8import os,timepwd = os.path.dirname(__file__) #获取当前文件夹的绝对路径pull_host_cmd = 'a ...

  6. 《android开发艺术探索》读书笔记(十五)--Android性能优化

    接上篇<android开发艺术探索>读书笔记(十四)--JNI和NDK编程 No1: 如果<include>制定了这个id属性,同时被包含的布局文件的根元素也制定了id属性,那 ...

  7. php基本函数的学习(2)

    chgrp chgrp — 改变文件所属的组 说明: bool chgrp ( string $filename , mixed $group ) 尝试将文件 filename 所属的组改成 grou ...

  8. HashMap并发导致死循环 CurrentHashMap

    为何出现死循环简要说明 HashMap闭环的详细原因 cocurrentHashMap的底层机制 为何出现死循环简要说明 HashMap是非线程安全的,在并发场景中如果不保持足够的同步,就有可能在执行 ...

  9. js中对一组数组进行求和

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. rpm检验是否被改动过

    参考原文http://vbird.dic.ksu.edu.tw/linux_basic/0520rpm_and_srpm.php#rpmmanager_verify   rpm -qVa   (当然可 ...