题目
思路
将 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;
}