题目描述

如果一个字符串可以被拆分为 $AABB$ 的形式,其中 $A$ 和 $B$ 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。

例如,对于字符串 aabaabaa,如果令 $A = \mathrm{aab}$,$B = \mathrm{a}$,我们就找到了这个字符串拆分成 $AABB$ 的一种方式。

一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令 $A=\mathrm{a}$,$B=\mathrm{baa}$,也可以用 $AABB$ 表示出上述字符串;但是,字符串 abaabaa 就没有优秀的拆分。

现在给出一个长度为 $n$ 的字符串 $S$,我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

以下事项需要注意:

  1. 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。
  2. 在一个拆分中,允许出现 $A=B$。例如 cccc 存在拆分 $A=B=\mathtt{c}$。
  3. 字符串本身也是它的一个子串。

输入格式

每个输入文件包含多组数据。输入文件的第一行只有一个整数 $T$,表示数据的组数。保证 $1 \le T \le 10$。

接下来 $T$ 行,每行包含一个仅由英文小写字母构成的字符串 $S$,意义如题所述。

输出格式

输出 $T$ 行,每行包含一个整数,表示字符串 $S$ 所有子串的所有拆分中,总共有多少个是优秀的拆分。

限制与约定

对于全部的测试点,保证 $1 \le T \le 10$。以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的 $T$ 组数据均满足限制条件。

我们假定 $n$ 为字符串 $S$ 的长度,每个测试点的详细数据范围见下表:

测试点编号 $n$ 其他约束
1、2 $\leq 300$ $S$中所有字符全部相同
3、4 $\leq 2000$
5、6 $\leq 10$
7、8 $\leq 20$
9、10 $\leq 30$
11、12 $\leq 50$
13、14 $\leq 100$
15 $\leq 200$
16 $\leq 300$
17 $\leq 500$
18 $\leq 1000$
19 $\leq 2000$
20 $\leq 30000$

暴力分析

似乎暴力就有95分啊?

先\(O(n^2)\)预处理双hash,用来判断子串是否相同

然后\(O(n^2)\)处理\(f[i]\),\(f[i]\)表示结尾位置为\(i\),满足\(AA\)的子串数量

然后可以直接根据\(f[i]\)在\(O(n^2)\)的的时间内得到\(g[i]\),\(g[i]\)表示,结尾为i满足\(AABB\)的子串数量

很基础啊?

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
#include<climits>
#include<vector>
#include<cmath>
#include<map>
#define LL long long using namespace std; inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
return *p1++;
} inline void read(int &x){
char c=nc();int b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
} inline void read(LL &x){
char c=nc();LL b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
} inline int read(char *s)
{
char c=nc();int len=1;
for(;!(c>='a' && c<='z');c=nc()) if (c==EOF) return 0;
for(;(c>='a' && c<='z');s[len++]=c,c=nc());
s[len++]='\0';
return len;
} inline void read(char &x){
for (x=nc();!(x>='A' && x<='Z');x=nc());
} int wt,ss[19];
inline void print(int x){
if (x<0) x=-x,putchar('-');
if (!x) putchar(48); else {
for (wt=0;x;ss[++wt]=x%10,x/=10);
for (;wt;putchar(ss[wt]+48),wt--);}
}
inline void print(LL x){
if (x<0) x=-x,putchar('-');
if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
} int T,n,f1[2010][2010],f2[2010][2010],f[2010],g[2010];
char s[2010];
const int mo1=100271,mo2=500179; void init()
{
memset(f1,0,sizeof(f1));
memset(f2,0,sizeof(f2));
for (int i=1;i<=n;i++)
{
for (int j=i;j<=n;j++)
f1[i][j]=(f1[i][j-1]*28%mo1+s[j]-'a'+1)%mo1,
f2[i][j]=(f2[i][j-1]*28%mo2+s[j]-'a'+1)%mo2;
}
} int main()
{
read(T);
while (T--)
{
read(s);
n=strlen(s+1);
init();
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for (int i=1;i<=n;i++)
{
int j,x;
if (i%2==1) j=2;else j=1;
for (;j<=i-1;j+=2)
{
x=i-j+1;x=j+x/2-1;
if (f1[j][x]==f1[x+1][i] && f2[j][x]==f2[x+1][i]) f[i]++;
}
}
for (int i=3;i<=n;i++)
{
int s=f[i-1],j=i-2,x;
for (int j=i+1;j<=n;j+=2)
{
x=j-i+1;x=i+x/2-1;
if (f1[i][x]==f1[x+1][j] && f2[i][x]==f2[x+1][j]) g[j]+=s;
}
}
int ans=0;
for (int i=4;i<=n;i++)
ans+=g[i];
print(ans),puts("");
}
return 0;
}

满分算法分析

当时考场上没打算为了这5分再去思考啊

不过正解的思想还是很不错的

基于上面的思想,我们可以看到$$ans=\sum_{i=1}^{i<n} f[i]*g[i+1]$$其中\(f[i]\)表示以第\(i\)位作为结束位的形如\(AA\)的个数,\(g[i]\)表示以第\(i\)位作为开始位的形如\(AA\)的个数

现在的问题就是怎么快速的求\(f[i]\)和\(g[i]\)

在UOJ群上围观了Claris秒题以后,大概知道了怎么弄QAQ

我们枚举\(AA\)串中\(A\)的长度\(L\)。在原串上,我们每隔\(L\)设置一个关键点,可以发现,若\(A\)的长度为\(L\),\(AA\)必定恰好经过某两个相邻的关键点。于是我们可以枚举\(AA\)经过的关键点

考虑两个相邻的关键点\(a,b\),有\(b=a+L\),我们求出\(a,b\)的最长公共前缀\(p\)和最长公共后缀\(s\)。若\(p+s>L\),\(AA\)串就一定存在,可以画个图来直观理解一下

那么,我们就可以直接得出可行的开始位置的区间为\([a-s+1,a+p-l]\),可行的结束位置为\([b-s+l,b+p-1]\)

直接暴力枚举的时间复杂度是\(T(n)=\sum_{i=1}^{n} \frac{n}{i}=nlogn\)


现在还有一个问题是怎么求\(a,b\)的最长公共前缀\(p\)和最长公共后缀\(s\)

首先可以看一下uoj35,他所求的是相邻\(rank\)的LCP

我们假设\(h[i]=LCP\{suffix(sa[i-1]),suffix(sa[i])\}\)

可以得到对于任意的\(j\)和\(k\)(假设\(rank[j]<rank[k]\)),\(LCP\{suffix(j),suffix(k)\}=min\{h[rank[j]+1],h[rank[j]+2],\cdots ,height[rank[k]]\}\)

直接用ST预处理,每次询问都是\(O(1)\),对上述复杂度无影响

PS.注意,我的代码使用SAM来构造SA的,由于SAM建立的时候会有新的节点产生,所以数组需要开大一些,不然会gg


然后最后的一份问题就是要进行区间加1的操作,直接差分即可,不要再往复杂度上加无谓的log

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<string>
#include<climits>
#include<vector>
#include<cmath>
#include<map>
#define LL long long using namespace std; inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
return *p1++;
} inline void read(int &x){
char c=nc();int b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
} inline void read(LL &x){
char c=nc();LL b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
} inline int read(char *s)
{
char c=nc();int len=0;
for(;!(c>='a' && c<='z');c=nc()) if (c==EOF) return 0;
for(;(c>='a' && c<='z');s[len++]=c,c=nc());
s[len++]='\0';
return len;
} inline void read(char &x){
for (x=nc();!(x>='A' && x<='Z');x=nc());
} int wt,ss[19];
inline void print(int x){
if (x<0) x=-x,putchar('-');
if (!x) putchar(48); else {
for (wt=0;x;ss[++wt]=x%10,x/=10);
for (;wt;putchar(ss[wt]+48),wt--);}
}
inline void print(LL x){
if (x<0) x=-x,putchar('-');
if (!x) putchar(48); else {for (wt=0;x;ss[++wt]=x%10,x/=10);for (;wt;putchar(ss[wt]+48),wt--);}
} int n,m,s,b[80010],c[80010],d[80010],f[80010],g[80010];
char sx[80010];
struct data
{
int len,fa,letter[26],tree[26],id,flag;
}a[80010];
int sa[80010],rank[80010],r1[80010],r2[80010],RANK,f1[80010][20],f2[80010][20]; void Extend(int x,int p)
{
s++;int q=s;a[q].len=a[p].len+1;
while (p!=0 && a[p].letter[x]==0)
a[p].letter[x]=q,p=a[p].fa;
if (p==0) {a[q].fa=1;return ;}
int np=a[p].letter[x];
if (a[np].len==a[p].len+1) a[q].fa=np;
else
{
s++;int nq=s;a[nq].len=a[p].len+1;
for (int i=0;i<26;i++)
a[nq].letter[i]=a[np].letter[i];
a[nq].id=a[np].id;
a[nq].fa=a[np].fa;a[np].fa=nq;a[q].fa=nq;
while (p!=0&&a[p].letter[x]==np)
a[p].letter[x]=nq,p=a[p].fa;
}
} void Insert(char x[])
{
int y=strlen(x);
s=1;int z=1;
for (int i=y-1;i>=0;i--)
Extend(x[i]-'a',z),z=a[z].letter[x[i]-'a'],a[z].id=i+1,d[i+1]=z,a[z].flag=1;
} void dfs(int x)
{
if (RANK>=30000)
print(1);
if(a[x].id!=0 && a[x].flag) sa[++RANK]=a[x].id,rank[a[x].id]=RANK;
for (int i=0;i<26;i++)
if (a[x].tree[i]!=0) dfs(a[x].tree[i]);
} void build()
{
for (int i=1;i<=s;i++)
c[a[i].len]++;
for (int i=1;i<=s;i++)
c[i]+=c[i-1];
for (int i=1;i<=s;i++)
b[c[a[i].len]--]=i;
for (int i=s;i>=1;i--)
{
int p=b[i];
a[a[p].fa].tree[sx[a[p].id+a[a[p].fa].len-1]-'a']=p;
}
RANK=0;
dfs(1);
} LL Query(int x,int y)
{
if (x==y) return a[x].len;
if (a[x].len>a[y].len) return Query(a[x].fa,y);
else return Query(x,a[y].fa);
} void SWAP(char *s)
{
int x=strlen(s);
for (int i=0;i<x/2;i++)
swap(s[i],s[x-i-1]);
} void init()
{
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
memset(sa,0,sizeof(sa));
memset(rank,0,sizeof(rank));
} int query1(int z,int y)
{
z=r1[z],y=r1[y];
if (z>y) swap(z,y);z++;
int x=(int)(log(y-z+1)/log(2));
return min(f1[z][x],f1[y-(1<<x)+1][x]);
} int query2(int z,int y)
{
z=r2[z],y=r2[y];
if (z>y) swap(z,y);z++;
int x=(int)(log(y-z+1)/log(2));
return min(f2[z][x],f2[y-(1<<x)+1][x]);
} void sa_init()
{
n=strlen(sx);
init();
Insert(sx);
build();
for (int i=2;i<=n;i++)
f1[i][0]=Query(d[sa[i]],d[sa[i-1]]);
f1[1][0]=0;
for (int j=1;1<<j<=n;j++)
for (int i=1;i+(1<<j)-1<=n;i++)
f1[i][j]=min(f1[i][j-1],f1[i+(1<<j-1)][j-1]);
for (int i=1;i<=n;i++)
r1[i]=rank[i];
init();
SWAP(sx);
Insert(sx);
build();
for (int i=2;i<=n;i++)
f2[i][0]=Query(d[sa[i]],d[sa[i-1]]);
f2[1][0]=0;
for (int j=1;1<<j<=n;j++)
for (int i=1;i+(1<<j)-1<=n;i++)
f2[i][j]=min(f2[i][j-1],f2[i+(1<<j-1)][j-1]);
for (int i=1;i<=n;i++)
r2[n-i+1]=rank[i];
} int T; int main()
{
read(T);
while (T--)
{
read(sx);
int m=strlen(sx),x,y,s,p;
sa_init();
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
for (int i=1;i<=m;i++)
{
x=1,y=x+i;
while(y<=m)
{
p=min(query1(x,y),i);
s=min(query2(x,y),i);
if (p+s>i)
{
f[x-s+1]++;f[x+p-i+1]--;
g[y-s+i]++;g[y+p]--;
}
x+=i,y+=i;
}
}
int F=0,G=0;
for (int i=1;i<=m;i++)
F+=f[i],f[i]=F,G+=g[i],g[i]=G;
LL ans=0;
for (int i=1;i<m;i++)
ans+=(LL)g[i]*f[i+1];
print(ans),puts("");
}
return 0;
}

【NOI2016】优秀的拆分的更多相关文章

  1. [NOI2016]优秀的拆分&&BZOJ2119股市的预测

    [NOI2016]优秀的拆分 https://www.lydsy.com/JudgeOnline/problem.php?id=4650 题解 如果我们能够统计出一个数组a,一个数组b,a[i]表示以 ...

  2. luogu1117 [NOI2016]优秀的拆分

    luogu1117 [NOI2016]优秀的拆分 https://www.luogu.org/problemnew/show/P1117 后缀数组我忘了. 此题哈希可解决95分(= =) 设\(l_i ...

  3. 【BZOJ4560】[NOI2016]优秀的拆分

    [BZOJ4560][NOI2016]优秀的拆分 题面 bzoj 洛谷 题解 考虑一个形如\(AABB\)的串是由两个形如\(AA\)的串拼起来的 那么我们设 \(f[i]\):以位置\(i\)为结尾 ...

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

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

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

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

  6. 题解-NOI2016 优秀的拆分

    NOI2016 优秀的拆分 \(T\) 组测试数据.求字符串 \(s\) 的所有子串拆成 \(AABB\) 形式的方案总和. 数据范围:\(1\le T\le 10\),\(1\le n\le 3\c ...

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

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

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

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

  9. [Noi2016]优秀的拆分

    来自F allDream的博客,未经允许,请勿转载,谢谢. 如果一个字符串可以被拆分为 AABB 的形式,其中 A和 B是任意非空字符串,则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 aab ...

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

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

随机推荐

  1. java中利用正则表达式获取a标签

    // 设置新闻内容 notice.setContent(editorValue); Matcher m = Pattern.compile("<a[^>]*>([^< ...

  2. python 多版本的兼容

    1.针对linux版本 linux版本的话,首先调用whereis python 来获取到多版本的路径. root@Ulord-14:~# whereis pythonpython: /usr/bin ...

  3. UVa 11806 - Cheerleaders (组合计数+容斥原理)

    <训练指南>p.108 #include <cstdio> #include <cstring> #include <cstdlib> using na ...

  4. Android通过onDraw实现在View中绘图操作

    Android绘图操作,通过继承View实现,在onDraw函数中实现绘图. 下面是一个简单的例子: public class AndroidTest extends Activity { /** C ...

  5. 团队Alpha版本冲刺(四)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:丹丹 组员7:何家伟 组员8:政演 组员9:鸿杰 组员10:刘一好 组员:何宇恒 展示组内最新 ...

  6. Redux & React & react-redux

    Redux Redux & React & react-redux https://redux.js.org/ https://redux.js.org/api https://red ...

  7. GYM - 101147 C.The Wall

    题意: 长和宽分别为M+N/2,N的矩形中.有很多敌人的点.有两种方法消灭敌人. 1.N个桶,第i个桶可以消灭i-1<=x<i中的敌人.2.M个摆(半圆)每个摆可以消灭距离他前面不超过1以 ...

  8. BZOJ1079 [SCOI2008]着色方案 【dp记忆化搜索】

    题目 有n个木块排成一行,从左到右依次编号为1~n.你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块. 所有油漆刚好足够涂满所有木块,即c1+c2+-+ck=n.相邻两个木块涂相同色显得很难看 ...

  9. NOIP2017赛前模拟11月6日—7日总结

    收获颇丰的两天··· 题目1:序列操作 给定n个非负整数,进行m次操作,每次操作给出c,要求找出c个正整数数并将它们减去1,问最多能进行多少操作?n,m<=1000000 首先暴力贪心肯定是每次 ...

  10. 怎么用dos命令进入指定的文件夹

    在正常开发中经常需要我们进入指定的文件夹下面的例子演示了进入这个文件夹D:\portal\liferay-portal-tomcat-5.5-4.4.0的dos命令 win+R---->输入cm ...