【NOI2016】優秀的拆分


題目描述

如果一個字符串可以被拆分為 $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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM