bzoj4556【TJOI2016&HEOI2016】字符串

4556: [Tjoi2016&Heoi2016]字符串

Time Limit: 20 Sec  Memory Limit: 128 MB
Submit: 195  Solved: 103
[Submit][Status][Discuss]

Description

佳媛姐姐過生日的時候,她的小夥伴從某東上買了一個生日禮物。生日禮物放在一個神奇的箱子中。箱子外邊寫了
一個長爲n的字符串s,和m個問題。佳媛姐姐必須正確回答這m個問題,才能打開箱子拿到禮物,升職加薪,出任CE
O,嫁給高富帥,走上人生巔峯。每個問題均有a,b,c,d四個參數,問你子串s[a..b]的所有子串和s[c..d]的最長公
共前綴的長度的最大值是多少?佳媛姐姐並不擅長做這樣的問題,所以她向你求助,你該如何幫助她呢?

Input

輸入的第一行有兩個正整數n,m,分別表示字符串的長度和詢問的個數。接下來一行是一個長爲n的字符串。接下來
m行,每行有4個數a,b,c,d,表示詢問s[a..b]的所有子串和s[c..d]的最長公共前綴的最大值。1<=n,m<=100,000,
字符串中僅有小寫英文字母,a<=b,c<=d,1<=a,b,c,d<=n

Output

 對於每一次詢問,輸出答案。

Sample Input

5 5
aaaaa
1 1 1 5
1 5 1 1
2 3 2 3
2 4 2 3
2 3 2 4

Sample Output

1
1
2
2
2



方法一:後綴自動機+二分答案+倍增+可持久化線段樹

後綴自動機的性質是,所有接收狀態都表示一個後綴。但題目要求最長公共前綴,所以要將字符串翻轉。(字符串翻轉的小技巧要掌握...)

於是問題轉化爲:給定兩個子串a和b,求的a所有子串和b的最長公共後綴的最大值。

然後二分答案,問題轉化爲判斷mid是否合法。

判斷方法:每個點用一個權值線段樹記錄right集合。在parent樹上倍增,找到mx值第一個包含mid的地方,然後用該點的線段樹判斷。

這裏又遇到另一個問題,線段樹的合併。可以證明線段樹的合併複雜度爲O(n*logn)。

具體證明:http://wenku.baidu.com/link?url=5HN1zM0kty7nAycmsZAYdRNSNLEb_Keepc-e-BNS3fLWc-0e4ON_bdvMtUD_w01ZIkWfbuKGh2LiXBXkbZ04vPJJTXdd7Dlm2OSO-_8gMgG

最終複雜度爲O(n*log^2n)。


#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define N 200005
#define M 4000005
using namespace std;
int n,m;
int pos[N];
int last=1,cnt=1,a[N][26],fa[N][20],mx[N],c[N],q[N];
int tot,rt[N],ls[M],rs[M];
char s[N];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void insert(int &k,int l,int r,int x)
{
	k=++tot;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (x<=mid) insert(ls[k],l,mid,x);
	else insert(rs[k],mid+1,r,x);
}
int merge(int x,int y)//線段樹的合併 
{
	if (!x||!y) return x+y;
	int z=++tot;
	ls[z]=merge(ls[x],ls[y]);rs[z]=merge(rs[x],rs[y]);
	return z;
}
bool find(int k,int l,int r,int L,int R)
{
	if (!k) return 0;
	if (l==L&&r==R) return 1;
	int mid=(l+r)>>1;
	if (R<=mid) return find(ls[k],l,mid,L,R);
	else if (L>mid) return find(rs[k],mid+1,r,L,R);
	else return find(ls[k],l,mid,L,mid)||find(rs[k],mid+1,r,mid+1,R);
}
void add(int x,int id)
{
	int p=last,np=++cnt;last=np;
	mx[np]=mx[p]+1;pos[id]=np;insert(rt[np],1,n,id);
	while (p&&!a[p][x]) a[p][x]=np,p=fa[p][0];
	if (!p) fa[np][0]=1;
	else
	{
		int q=a[p][x];
		if (mx[q]==mx[p]+1) fa[np][0]=q;
		else
		{
			int nq=++cnt;mx[nq]=mx[p]+1;
			memcpy(a[nq],a[q],sizeof(a[q]));
			fa[nq][0]=fa[q][0];fa[np][0]=fa[q][0]=nq;
			while (a[p][x]==q) a[p][x]=nq,p=fa[p][0];
		}
	}
}
void calc()
{
	F(i,1,cnt) c[mx[i]]++;
	F(i,1,cnt) c[i]+=c[i-1];
	F(i,1,cnt) q[c[mx[i]]--]=i;
	D(i,cnt,1)
	{
		int x=q[i],f=fa[x][0];
		rt[f]=merge(rt[f],rt[x]);
	}
	F(j,1,18) F(i,1,cnt) fa[i][j]=fa[fa[i][j-1]][j-1];
}
bool check(int mid,int x,int l,int r)
{
	D(i,18,0) if (mx[fa[x][i]]>=mid) x=fa[x][i];
	return find(rt[x],1,n,l,r);
}
int main()
{
	n=read();m=read();
	scanf("%s",s+1);reverse(s+1,s+n+1);
	F(i,1,n) add(s[i]-'a',i);
	calc();
	F(i,1,m)
	{
		int a=n-read()+1,b=n-read()+1,c=n-read()+1,d=n-read()+1;
		swap(a,b);swap(c,d);
		int l=1,r=min(d-c+1,b-a+1),mid,ans=0;
		while (l<=r)
		{
			mid=(l+r)>>1;
			if (check(mid,pos[d],a+mid-1,b)) ans=mid,l=mid+1;
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}



方法二:後綴數組

這個方法感覺是暴力,但是跑得飛快...

每次在後綴數組上暴力向前和向後找,當height[i]≤ans時停止搜索,相當於一個小的剪枝。


#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define N 200005
#define inf 1000000000
using namespace std;
int n,m;
int x[N],y[N],c[N],sa[N],rnk[N],h[N];
char s[N];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void build_sa()
{
	int num=26,t;
	F(i,1,n) c[x[i]]++;
	F(i,2,num) c[i]+=c[i-1];
	D(i,n,1) sa[c[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1)
	{
		t=0;
		F(i,n-k+1,n) y[++t]=i;
		F(i,1,n) if (sa[i]>k) y[++t]=sa[i]-k;
		memset(c,0,sizeof(c));
		F(i,1,n) c[x[i]]++;
		F(i,2,num) c[i]+=c[i-1];
		D(i,n,1) sa[c[x[y[i]]]--]=y[i];
		swap(x,y);
		t=1;x[sa[1]]=1;
		F(i,2,n) x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])?t:++t;
		if (t>=n) break;
		num=t;
	}
}
void get_h()
{
	int tmp=0,x;
	F(i,1,n) rnk[sa[i]]=i;
	F(i,1,n)
	{
		x=sa[rnk[i]-1];
		if (tmp) tmp--;
		while (s[x+tmp]==s[i+tmp]) tmp++;
		h[rnk[i]]=tmp;
	}
}
int main()
{
	n=read();m=read();
	scanf("%s",s+1);
	F(i,1,n) x[i]=s[i]-'a'+1;
	build_sa();
	get_h();
	F(i,1,m)
	{
		int a=read(),b=read(),c=read(),d=read();
		int pos=rnk[c],ans=0,mn=inf;
		if (a<=c&&c<=b) ans=min(d-c+1,b-c+1);
		D(i,pos,2)
		{
			if (h[i]<=ans) break;
			mn=min(mn,h[i]);
			if (sa[i-1]>=a&&sa[i-1]<=b) ans=max(ans,min(mn,min(d-c+1,b-sa[i-1]+1)));
		}
		mn=inf;
		F(i,pos+1,n)
		{
			if (h[i]<=ans) break;
			mn=min(mn,h[i]);
			if (sa[i]>=a&&sa[i]<=b) ans=max(ans,min(mn,min(d-c+1,b-sa[i]+1)));
		}
		printf("%d\n",ans);
	}
	return 0;
}


發佈了417 篇原創文章 · 獲贊 21 · 訪問量 82萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章