父亲醉酒

醉酒

drunk.pas/c/cpp)

【题目描述】

X家住在一条东西向的大街上。在他们家的东面,自东向西依次分布着n幢相似的房屋,编号为1到n,可以用一个长度为n的字符串表示这n幢房屋的颜色。

X的父亲经常喝醉酒,每次醉酒后他都会稀里糊涂地走到第Ai幢房屋前,然后坚信他现在第Bi幢房屋前。虽然他喝醉了,但是他依然能分清每幢房屋的颜色,并且掏出地图进行比较,只有房屋颜色和地图上一致时,他才会继续向西走。当他发现自己的错误或者不能前进时,就会停下来。现在,小X记录下了每次的数据Ai和Bi,他希望知道父亲最多走过多少幢相同颜色的房屋。

【输入格式】

第一行两个数n和m,表示n幢房屋和m次询问。

第二行一个长度为n的字符串,仅包含大写字母。

接下来m行,每行两个数Ai和Bi。

【输出格式】

m行,每个一个数,表示小X的父亲父亲最多走过多少幢相同颜色的房屋。

【输入样例】

10 6

AABBCCAABB

1 7

7 1

3 4

4 9

2 5

5 5

【输出样例】

4

4

1

1

0

6

【数据范围】

对于60%的数据,n,m≤100。

对于80%的数据,n≤1000。

对于100%的数据,n,m≤100000,Ai,Bi≤n。


分析:

裸的字符串最长公共前缀,后缀数组即可。当然,二分加hash也是可以的。

后缀数组基础。 字符串匹配hash

参考程序:

#include<cstdio>
#include<algorithm>
#define maxn 210000
using namespace std;
int rank[maxn],sa[maxn],tmp[maxn],lcp[maxn],a[maxn];
int f[maxn][25];
int n,m,k;
bool cmp_sa(int i,int j){
	if (rank[i] != rank[j])return rank[i]<rank[j];
	int ri=i+k<=n?rank[i+k]:-1;
    int rj=j+k<=n?rank[j+k]:-1;
    return ri<rj;	
}
int query(int x,int y){
	int k=0;
	while ((1<<(k+1))<=y-x+1)k++;
	//printf("%d %d %d %d %d %d\n",x,y,y-(1<<k)+1,k,f[x][k],f[y-(1<<k)+1][k]);
	return min(f[x][k],f[y-(1<<k)+1][k]);
}
int main(){
	freopen("drunk.in","r",stdin);
	freopen("drunk.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=0;i<n;i++){
		char cmd=getchar();
		while(!('A'<=cmd && cmd<='Z'))cmd=getchar();
		rank[i]=cmd-'A',sa[i]=i,a[i]=cmd-'A'+1;
	}
	sa[n]=n;
	rank[n]=-1;
	for (k=1;k<=n;k<<=1){
		sort(sa,sa+n+1,cmp_sa);
		tmp[sa[0]]=0;
		for (int i=1;i<=n;i++)
			tmp[sa[i]]=tmp[sa[i-1]]+(cmp_sa(sa[i-1],sa[i])?1:0);
		for (int i=0;i<=n;i++)rank[i]=tmp[i];
	}
	/*for (int i=0;i<=n;i++){
		printf("%d %d %d\n",i,sa[i],rank[i]);
		for (int j=sa[i];j<n;j++)
			printf("%d ",a[j]);
		printf("\n\n");
	}*/
	int h=0;
	for (int i=0;i<=n;i++)rank[sa[i]]=i;
	lcp[0]=0;
	for (int i=0;i<n;i++){
		int j=sa[rank[i]-1];
		//printf("LCP %d %d\n",i,j);
		if (h>0)h--;
		for (;i+h<n && j+h<n;h++)
			if (a[i+h]!=a[j+h])break;
		lcp[rank[i]-1]=h;
	}
	for (int i=0;i<=n;i++)f[i][0]=lcp[i];
	//printf("I  F  LCP  SA  RANK\n");
	//for (int i=0;i<=n;i++)printf("%d %d %d %d %d\n",i,f[i][0],lcp[i],sa[i],rank[i]);
	for (int j=1;(1<<j)<=n;j++)
		for (int i=1;i+(1<<j)-1<=n;i++)
			f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);
	/*for (int i=0;i<=n;i++){
		printf("CASE %d:",i);
		for (int j=0;i+(1<<j)-1<=n;j++)
			printf("%d ",f[i][j]);
		printf("\n");
	}*/
	while (m--){
		int x,y;
		scanf("%d%d",&x,&y);x--;y--;
		if (x==y){printf("%d\n",n-x);continue;}
		//printf("%d %d,\n",x,y);
		if (rank[x]>rank[y])swap(x,y);
		printf("%d\n",query(rank[x],rank[y]-1));
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值