[CEOI2011]Matching

137 篇文章 1 订阅
38 篇文章 0 订阅
本文探讨了一种使用KMP算法优化问题,将离散化序列与特定模式aaa进行匹配的高效方法。通过哈希和树状数组的传统解决方案后,作者提出利用KMP的思想,关注数字间的关系,借助值域特点设计线性时间复杂度的O(n+m)算法。
摘要由CSDN通过智能技术生成

题目

传送门 to luogu

传送门 to LOJ

思路

p p p 转化一下,令 a p i = i a_{p_i}=i api=i 。显然 a a a 序列是符合 p p p 序列的。

只要符合 p p p 序列,那就会和 a a a 类似。问题即,有多少个长度为 n n n 的区间,其离散化后为 a a a 序列。

显然我们可以考虑哈希,每次插入一个数会导致比它大的数字的离散化结果变大 1 1 1,也就是对应位置的 b a s e k base^k basek 的求和,可以用树状数组维护,删除同理,时间复杂度 O ( m log ⁡ m ) \mathcal O(m\log m) O(mlogm)

有没有更好的做法?有。只有一个模式串、一个文本串,当然会想起 K M P \tt KMP KMP 。此题能不能 K M P \tt KMP KMP 呢?

K M P \tt KMP KMP 的要点,并不在于定义相等,而是 可延展性。也就是说, S S S T T T 匹配,等价于 S S S T T T 同时去掉最后一个元素之后匹配,并且最后一个元素是匹配的。

在本题里,“离散化后与 a a a 序列匹配” 并不是可延展的——最后一个元素是否加入,会影响前面的元素的离散化结果。怎么办?

稍微修改一下:当前数组离散化后,需要与 a a a 序列 离散化 结果相同。也就是说,当前序列元素之间的大小关系,和 a a a 序列是相同的。然后就可以 K M P \tt KMP KMP 啦!

如何判断新加入的数的离散化情况?或许我们可以直接求出排名,但这样又需要 B I T \tt BIT BIT 。这里就有一个不易想到的优化点了:由于 a a a 序列的值域是 O ( n ) \mathcal O(n) O(n) 的,考虑利用这一点求出 a a a 序列(的所有前缀)的大小关系,然后代入当前序列检验。

a a a 序列怎么求大小关系?而且是对于所有前缀都求一次?只需要像 链表 一样,求出当前数字在前缀中的前驱、后继(前缀中恰好比自己大的、恰好比自己小的),那么大小关系就明朗了。

如果当前序列的离散化结果和 a a a 离散化结果相同,那么每个数字都要大于 a a a 中前驱对应的数字,并且小于 a a a 中后继对应的数字。这不仅是必要条件,更是充分条件。这样就可以做到 O ( 1 ) \mathcal O(1) O(1) 判断了。

可是这东西究竟怎么求呢?显然要利用值域的特点。可以想象一下:数轴上有 0 , 1 0,1 0,1 两种数字,目的是求一个数字旁边第一个 1 1 1,并且可以 单向修改 0 0 0 改为 1 1 1 。这怎么做?

将过程从后往前进行,则只会把 1 1 1 改成 0 0 0,询问第一个 1 1 1 的位置。很像并查集,对吧?甚至比并查集更简单,因为询问一定在 1 1 1 上进行,即只会询问极长连续 0 0 0 子段的端点,那么将这些极长段维护下来,问题就解决了!

这是 O ( n + m ) \mathcal O(n+m) O(n+m) 的线性做法。我在往这个方向思考的时候,至少想错了三次

代码

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

const int MaxN = 1000005;
int l[MaxN], r[MaxN], pre[MaxN], suf[MaxN];
int a[MaxN], b[MaxN], nxt[MaxN];
int ans[MaxN], tot;
int main(){
	int n = readint(), m = readint();
	rep(i,1,n){
		a[b[i] = readint()] = i;
		l[i] = i+1, r[i] = i-1;
	}
	l[0] = 1, r[n+1] = n; // boundary
	drep(i,n,1){
		/* remove a[i] from list */ ;
		int L = l[a[i]-1], R = r[a[i]+1];
		r[L] = R, l[R] = L; // only endpoint
		/* query pre and suf */ ;
		pre[i] = b[L-1], suf[i] = b[R+1];
	}
	rep(i,1,m) b[i] = readint();
	for(int j=nxt[1]=0,i=2; i<=n; ++i){
		while(j != 0){
			int x = j+1-pre[j+1];
			if(pre[j+1] && a[i] < a[i-x]){
				j = nxt[j]; continue;
			}
			x = j+1-suf[j+1];
			if(suf[j+1] && a[i] > a[i-x]){
				j = nxt[j]; continue;
			}
			++ j; break; // match
		}
		if(!j) ++ j; // 1 is always ok
		nxt[i] = j; // just like KMP
	}
	for(int j=0,i=1; i<=m; ++i){
		while(j != 0){
			int x = j+1-pre[j+1];
			if(pre[j+1] && b[i] < b[i-x]){
				j = nxt[j]; continue;
			}
			x = j+1-suf[j+1];
			if(suf[j+1] && b[i] > b[i-x]){
				j = nxt[j]; continue;
			}
			++ j; break; // match
		}
		if(!j) ++ j; // at least 1
		if(j == n){
			ans[++ tot] = i-n+1;
			j = nxt[j]; // manual
		}
	}
	writeint(tot), putchar('\n');
	rep(i,1,tot){
		writeint(ans[i]);
		putchar(' ');
	}
	putchar('\n');
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值