子串查询
Time Limit: 3500/3000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Problem Description
度度熊的字符串课堂开始了!要以像度度熊一样的天才为目标,努力奋斗哦!
为了检验你是否具备不听课的资质,度度熊准备了一个只包含大写英文字母的字符串 A[1,n] = ,接下来他会向你提出 qq 个问题 (l,r)(l,r),你需要回答字符串 A[l,r] = 内有多少个非空子串是 A[l,r]A[l,r] 的所有非空子串中字典序最小的。这里的非空子串是字符串中由至少一个位置连续的字符组成的子序列,两个子串是不同的当且仅当这两个子串内容不完全相同或者出现在不同的位置。
记 |S| 为字符串 S 的长度,对于两个字符串 S 和 T ,定义 S 的字典序比 T 小,当且仅当存在非负整数 使得 S 的前 k 个字符与 T 的前 k 个字符对应相同,并且要么满足 ,要么满足 且 S 的第 k+1 个字符比 T 的第 k+1 个字符小。例如 “AA” 的字典序比 “AAA” 小,”AB” 的字典序比 “BA” 小。
Input
第一行包含一个整数 T,表示有 T 组测试数据。
接下来依次描述 T 组测试数据。对于每组测试数据:
第一行包含两个整数 n 和 q,表示字符串的长度以及询问的次数。
第二行包含一个长为 n 的只包含大写英文字母的字符串 A[1,n]A[1,n]。
接下来 q 行,每行包含两个整数 ,表示第 ii 次询问的参数。
保证 , ,
Output
对于每组测试数据,先输出一行信息 “Case #x:”(不含引号),其中 x 表示这是第 xx 组测试数据,接下来 qq 行,每行包含一个整数,表示字符串 A[l,r]A[l,r] 中字典序最小的子串个数,行末不要有多余空格。
Sample Input
1
2 3
AB
1 1
1 2
2 2
Sample Output
Case #1:
1
1
1
Solution
既然题目说’AA’要比’AAA’小,’AB’要比’BA’小,那么’A’是不是<’AA’<’AB’呢?也就是说’A’是最小的。
所以题目大意可以转化为:给你一个长度为n字符串和q个询问,每个询问让你求出从l到r区间的最小的字母出现的次数。
那一看这不是很简单了嘛,直接上线段树,维护一个最小值及其出现次数。于是我兴奋的打了出来。
Code1
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#define N 100010
using namespace std;
int n,t,q,k,x,y,p,ans;
char s[N];
int tree[N*5],st[N*5][27];
void build(int l,int r,int x){
if(l==r){
tree[x]=s[l]-'A';
st[x][s[l]-'A']=1;
return;
}
int mid=(l+r)>>1;
build(l,mid,x*2);
build(mid+1,r,x*2+1);
tree[x]=min(tree[x*2],tree[x*2+1]);
for(int j=0;j<=25;j++){
st[x][j]=st[x*2][j]+st[x*2+1][j];
}
}
void tmin(int s,int t,int l,int r,int x){
if(s==l&&t==r){
p=min(p,tree[x]);
return;
}
int mid=(l+r)>>1;
if(t<=mid) tmin(s,t,l,mid,x*2);
else if(s>mid) tmin(s,t,mid+1,r,x*2+1);
else{
tmin(s,mid,l,mid,x*2);
tmin(mid+1,t,mid+1,r,x*2+1);
}
}
void qry(int s,int t,int l,int r,int x){
if(s==l&&t==r){
ans+=st[x][p];
return;
}
int mid=(l+r)>>1;
if(t<=mid) qry(s,t,l,mid,x*2);
else if(s>mid) qry(s,t,mid+1,r,x*2+1);
else{
qry(s,mid,l,mid,x*2);
qry(mid+1,t,mid+1,r,x*2+1);
}
}
int main(){
freopen("findsubstring.in","r",stdin);
freopen("findsubstring.out","w",stdout);
scanf("%d",&t);
for(int k=1;k<=t;k++){
printf("Case #%d:\n",k);
scanf("%d%d",&n,&q);
scanf("%s",s+1);
memset(tree,127,sizeof(tree));
memset(st,0,sizeof(st));
build(1,n,1);
for(int i=1;i<=q;i++){
scanf("%d%d",&x,&y);
ans=0;p=N;
tmin(x,y,1,n,1);
qry(x,y,1,n,1);
printf("%d\n",ans);
}
}
return 0;
}
一交上去果然AC,注意最小值和出现次数必须分开求,作者曾经在这里卡了2分钟,至于为啥,读者可以自己想一想,我给出一个数据,大家可以试一试:
input:
1
4 1
ZZAZ
1 3
output:
Case #1:
1
将最小值和求解最小出现次数合起来打的答案是3,因为他加上了1-2的两个Z。
但再一看题目感觉貌似可以不用线段树啊!?!于是我又想到了前缀和,l~r出现的次数,其实就等于1~r出现的次数减去1~(l-1)出现的次数,至于最小值嘛,1~26扫一遍,看看哪个最早出现,就输出哪个不就行了吗?于是又高兴的打了出来。
Code2
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 100010
using namespace std;
int t,n,q,x,y,len;
char s[N];
int a[N][27];
int main(){
freopen("findsubstring.in","r",stdin);
freopen("findsubstring.out","w",stdout);
scanf("%d",&t);
for(int k=1;k<=t;k++){
printf("Case #%d:\n",k);
scanf("%d%d",&n,&q);
scanf("%s",s+1);
len=strlen(s+1);
memset(a,0,sizeof(a));
for(int i=1;i<=len;i++){
for(int j=0;j<=25;j++) a[i][j]=a[i-1][j];
a[i][s[i]-'A']++;
}
for(int i=1;i<=q;i++){
scanf("%d%d",&x,&y);
for(int j=0;j<=25;j++){
if(a[y][j]-a[x-1][j]!=0){
printf("%d\n",a[y][j]-a[x-1][j]);
break;
}
}
}
}
return 0;
}
交上去也AC,居然这么简单?哎,一道前缀和的题目打了个线段树,我也是醉了。
作者:zsjzliziyang
QQ:1634151125
转载及修改请注明
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/81415925