醉酒
(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也是可以的。
参考程序:
#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;
}