P10634 BZOJ2372 music 个人题解
说句闲话:新换的题解志愿者审核速度太快了,好评!
题目大意:
给定两个数组 \(a\) 和 \(b\),要求 \(b\) 数组在 \(a\) 中类似的出现过几次,并求每一次的开始的位置
Solution:
首先我们要明白两个数组 \(a\) 与 \(b\) 类似有且仅当满足以下三个条件:
- \(a\) 与 \(b\) 的长度相同
- 如果存在 \(a_{i}=a_{j}\) 则一定存在 \(b_{i}=b_{j}\)
- 如果存在 \(a_{i}<a_{j}\) 则一定存在 \(b_{i}<b_{j}\),同理如果存在 \(a_{i}>a_{j}\) 则也一定存在 \(b_{i}>b_{j}\)
可以发现前两个条件与字符串查找这个题是一样的,我们实际上就是要保证两个数组的排列样式一致,即判断每个数字的位置关系一不一样。我们用两个数组来处理位置关系,一个 \(pre\) 数组来表示上一个相同的数字出现的位置,一个 \(dis\) 数组来表示当前的数字到上一个相同的数字的距离,然后问题就转化为求 \(b\) 数组的 \(dis\) 数组在 \(a\) 数组的 \(dis\) 数组中能匹配的次数及位置。
不过和那道题一样,为避免第一次出现的数字的 \(dis\) 数组为 \(0\) 导致匹配不上的情况,我们为它们设置一个通配符,即什么也可以匹配的上,我在这里用第一次出现的位置来作为它们的通配符。
然后现在的问题就很 KMP 了,KMP 正是求两个字符串一个在另一个中出现的次数以及位置的(不会 KMP 的推荐看这一篇题解的前半段),不过既然我们给那些第一次出现的数设置了一个通配符,那么在处理 \(disa_{i}\) 与 \(disb_{j+1}\) 以及 \(disb_{i}\) 和 \(disb_{j+1}\) 是否相等时要单独处理这两个是否为通配符,其实判断挺简单的,我们是用第一次出现的位置来设置的通配符,那直接比较当前的 \(dis\) 是否大于匹配的长度就可以了。
到这里你已经有了 \(36\) 的高分了,现在我们处理条件 \(3\),可以发现 \(s\leq 25\),那直接枚举其第一次出现的数字的大小关系不就行了,我们在处理 \(a\) 数组时用一个 \(pos\) 数组记录 \(dis_{i}\) 出现第几次的位置,在处理 \(b\) 数组时用一个 \(idx\) 数组保留每一次通配符的位置(即第一次出现的位置),并按照从 \(1\) 到 \(s\) 的顺序排好保存在 \(c\) 数组里,然后在每一次匹配到之后我们用 \(pos\) 数组枚举所有数字在当前匹配后的串内的位置,然后让其与 \(c\) 数组比较,如果一致则说明满足条件,存下来输出就行。
代码:
还要注意每次输出位置要换行。(这是一道输出毒瘤题)
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,M=30;
inline int read(){
int x=0,f=1;
char c=getchar();
while(c<'0' || c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0' && c<='9'){
x=x*10+c-'0';
c=getchar();
}
return x*f;
}
int n=read(),m=read(),s=read(),a[N],b[N];
int aa[N],bb[N],pre[N],nxt[N],pos[M][N],tot[M];
//aa[i]存a数组的dis,bb[i]存b数组的dis,pre数组处理上一个相同数字出现的位置
//nxt[i]数组是跑KMP,pos存aa数组每一次出现的位置,tot[i]数组记录aa数组出现的次数
int idx[M],cnt,c[N];
//idx[i]数组记录b数组中第一次出现的数的位置,c[i]数组把idx[i]数组按照1~s的顺序排序
vector<int> ans;
inline bool solve(int a,int b,int len){//比较是否相等同时判断是否有通配符
return (a==b) || (b>len && a>len);
}
int main(){
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<=m;i++)
b[i]=read();
for(int i=1;i<=n;i++){
aa[i]=i-pre[a[i]];
pre[a[i]]=i;
pos[a[i]][++tot[a[i]]]=i;
}
memset(pre,0,sizeof(pre));
for(int i=1;i<=m;i++){
bb[i]=i-pre[b[i]];
pre[b[i]]=i;
if(bb[i]==i)
idx[b[i]]=i;
}
for(int i=1;i<=s;i++)
if(idx[i])
c[++cnt]=idx[i];
for(int i=2,j=0;i<=m;i++){
while(j>0 && !solve(bb[i],bb[j+1],j))
j=nxt[j];
if(solve(bb[i],bb[j+1],j))
j++;
nxt[i]=j;
}
for(int i=1,j=0;i<=n;i++){
while(j>0 && !solve(aa[i],bb[j+1],j))
j=nxt[j];
if(solve(aa[i],bb[j+1],j))
j++;
if(j==m){
bool ok=false;
for(int k=1,cnt=0;k<=s;k++){//从小到大枚举
int num=1;
while(num<=tot[k] && pos[k][num]<i-m+1)//一次枚举k在串内第一次出现的位置
++num;
if(num>tot[k] || pos[k][num]>i)//如果不存在跳出
continue;
if(c[++cnt]!=pos[k][num]-(i-m+1)+1){//如果与原数组中的位置关系不符,则这次匹配无效,退出
ok=true;
break;
}
}
if(!ok)//如果满足条件3,加入答案
ans.push_back(i-m+1);
j=nxt[j];
}
}
printf("%d\n",ans.size());
for(int i=0;i<ans.size();i++)
printf("%d\n",ans[i]);
return 0;
}

浙公网安备 33010602011771号