题解 P4696 【[CEOI2011]Matching】

maowuyou

2019-08-19 14:29:46

Solution

# KMP + 重新定义相等 [题目传送器](https://www.luogu.org/problem/P4696) 题目很绕 , 其实就是一句话 #### 给你两个排列 P 、H, 求 H 中 任意一段 连续的 排列 与P相同的序列 怎么求 ????? ~~(白问)~~ 这很明显是一个匹配问题 具体得说 : 两个串进行匹配 KMP 就是 在线性时间 完成这种题目 的 算法 ### 考虑怎么 KMP ? 很容易想到 , 匹配数字串 和 匹配 字符串的大致思路没有差别 匹配 单纯的数字串 和 匹配 “排列串” 的差别 仅仅在只是在 判断相等 上有差别 ### 所以 匹配 “排列串” 和 比配 字符串的差别 在于 判断相等 现在所有人应该都能理解标题中的 “重新定义相等” 了 吧 #### 现在难点变为 : 如何重新定义 “相等” 以前看到过一句话 : 你定义两个事物的某个特性一样时,称两个事物相等 那么 你定义的标准 肯定是你 最看重的 P 是 一个排列 何为排列 ? 排序后的列的顺序 顺序与什么有关 ? “比他大的数的个数” 和 “比他小的数的个数 ” 所以相等就被定义为 在一个范围内 h[i] 与和其匹配的c[i] 有 一样多的 “比他大的数的个数” 和 “比他小的数的个数 ” 于是乎 离散化 + 树状数组 的解法 就出现了 但对于我这种蒟蒻而言 , 数据结构太难了!!! 所以我们换一种角度 其实很容易就能想到 c[i] 若满足 p[i] 的排列(即 c[i] 在这一段范围内排在与 p[i] 同样的位置) 也是能算作匹配的 于是我们预处理处 P 排列中 p[i] 的 前驱和后继 的位置 匹配时 ,判断 c[i] 是否 大于(或小于) 他所要匹配的 p[i] 的 前驱 (或后继) 这样的匹配是 O(1) 的 ~~似乎比 树状数组还快~~ 就形成了这个玄学算法 请各位仔细感性理解 代码如下 ```cpp #include<bits/stdc++.h> #define MAXN 1000005 int a[MAXN],b[MAXN],c[MAXN],p[MAXN]; int pre[MAXN],net[MAXN],L[MAXN],R[MAXN]; int ans[MAXN]; int n,m,k; bool check(int P[],int u,int v) { return P[v+L[u]]<=P[v] && P[v+R[u]]>=P[v]; } int main() { scanf("%d %d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&b[i]); a[b[i]]=i; pre[i]=i-1; net[i]=i+1; } for(int i=n;i>=1;i--) { int j=a[i]; if (pre[j]) L[i]=b[pre[j]]-i; if (net[j]<=n) R[i]=b[net[j]]-i; pre[net[j]]=pre[j]; net[pre[j]]=net[j]; } for(int i=2,j=0;i<=n;i++) { while (j && !check(a,j+1,i)) j=p[j]; if (check(a,j+1,i)) j++; p[i]=j; } for(int i=1;i<=m;i++) scanf("%d",&c[i]); for(int i=1,j=0;i<=m;i++) { while (j && !check(c,j+1,i)) j=p[j]; if (check(c,j+1,i)) j++; if (j==n) { ans[++k]=i-n+1; j=p[j]; } } printf("%d\n",k); if (k==0) { puts(""); return 0; } for(int i=1;i<=k;i++) printf("%d ",ans[i]); return 0; } ``` 希望这道题能让你更好理解KMP 理解 事物间的相等关系 理解匹配的真谛