P10634 BZOJ2372 music 个人题解

说句闲话:新换的题解志愿者审核速度太快了,好评!

题目链接

题目大意:

给定两个数组 \(a\)\(b\),要求 \(b\) 数组在 \(a\) 中类似的出现过几次,并求每一次的开始的位置

Solution:

首先我们要明白两个数组 \(a\)\(b\) 类似有且仅当满足以下三个条件:

  1. \(a\)\(b\) 的长度相同
  2. 如果存在 \(a_{i}=a_{j}\) 则一定存在 \(b_{i}=b_{j}\)
  3. 如果存在 \(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;
}
posted @ 2025-11-19 10:43  See_you_soon  阅读(5)  评论(0)    收藏  举报