对于整数序列 \((a_1, a_2, ..., a_n)\) 和 1 ~ n 的排列 \((p_1, p_2, ..., p_n)\),称 \((a_1, a_2, ..., a_n)\) 符合 \((p_1, p_2, ..., p_n)\),当且仅当:spa
(1){a} 中任意两个数字互不相同。
(2)将 \((a_1, a_2, ..., a_n)\) 从小到大排序后,将会获得 \((a_{p_1}, a_{p_2}, ..., a_{p_n})\)。code
如今给出 1 ~ n 的排列 {p} 与序列 \(h_1, h_2, ..., h_m\),请你求出哪些 h 的子串符合排列 {p}。排序
输入格式
第一行两个空格隔开的正整数 n, m。
第二行 n 个空格隔开的正整数,表示排列 p。
第三行 m 个空格隔开的正整数,表示序列 h。ip
输出格式
第一行一个整数 k,表示符合 {p} 的子串个数。
第二行 k 个空格隔开的正整数,表示这些子串的起始位置(编号从 1 开始)。请将这些位置按照从小到大的顺序输出。特别地,若 k = 0,那么你也应当输出一个空行。字符串
样例输入
5 10
2 1 5 3 4
5 6 3 8 12 7 1 10 11 9
样例输出
2
2 6get
数据范围与提示
2 <= n <= m <= 1000000; 1 <= hi <= 10^9; 1 <= pi <= n。
且保证 {h} 中元素互不相同,{p} 是一个排列。hash
先对问题做一步转化:求 {q} 使得 \(q_{p_i} = i\),即 {p} 的逆置换。
那么某个子串符合 {p} 能够等价于这个子串离散化到 1 ~ n 中后等于 q。it
若是不考虑离散化,那么就是一个经典子串匹配问题,直接上 kmp。
假如子串同构的断定方法如上,即离散化后同构,是否还能够扩展一下 kmp 呢?io
考虑 kmp 何时须要判同构:已知串 s 与串 t 同构时,在 s 末尾加一个 a,在 t 末尾加一个 b,判断 s + a 与 t + b 是否同构。
由于 s 与 t 已经同构了,只须要 a 与 b 加入进去事后仍然同构便可。
能够等价于断定 a 在 s 中的排名(s 中比 a 小的数) = b 在 t 中的排名(t 中比 b 小的数)。
查排名能够平衡树,不过这道题直接离散化 + 树状数组便可。
注意 kmp 在跳 fail 的时候,须要一个个元素的移动,由于要维护树状数组。
不过复杂度的证实是不会变的。kmp 仍是 O(n),套个树状数组就是 O(nlogn) 的。
#include <cstdio> #include <vector> #include <algorithm> using namespace std; const int MAXN = 1000000; int n, m; int t[2][MAXN + 5]; int lowbit(int x) {return x & -x;} void update(int x, int k, int type) { for(int i=x;i<=m;i+=lowbit(i)) t[type][i] += k; } int sum(int x, int type) { int ret = 0; for(int i=x;i;i-=lowbit(i)) ret += t[type][i]; return ret; } int d[MAXN + 5], p[MAXN + 5], h[MAXN + 5]; void discrete() { for(int i=1;i<=m;i++) d[i] = h[i]; sort(d + 1, d + m + 1); for(int i=1;i<=m;i++) h[i] = lower_bound(d + 1, d + m + 1, h[i]) - d; } int f[MAXN + 5]; void get_f() { f[1] = 0; int ri = 0, le = 2; for(int i=2;i<=n;i++) { int j = f[i-1]; while( sum(p[j+1], 0) != sum(p[i], 1) ) { while( ri != f[j] ) update(p[ri--], -1, 0), update(p[le++], -1, 1); j = f[j]; } f[i] = j + 1; update(p[++ri], 1, 0), update(p[i], 1, 1); } } vector<int>ans; void get_ans() { for(int i=1;i<=m;i++) t[0][i] = t[1][i] = 0; int le = 1, ri = 0, j = 0; for(int i=1;i<=m;i++) { while( sum(p[j+1], 0) != sum(h[i], 1) ) { while( ri != f[j] ) update(p[ri--], -1, 0), update(h[le++], -1, 1); j = f[j]; } j++; update(p[++ri], 1, 0), update(h[i], 1, 1); if( j == n ) { ans.push_back(i-n+1); while( ri != f[j] ) update(p[ri--], -1, 0), update(h[le++], -1, 1); j = f[j]; } } } int main() { scanf("%d%d", &n, &m); for(int i=1;i<=n;i++) { int x; scanf("%d", &x); p[x] = i; } for(int i=1;i<=m;i++) scanf("%d", &h[i]); discrete(), get_f(), get_ans(); printf("%d\n", (int)ans.size()); for(int i=0;i<(int)ans.size();i++) printf("%d%c", ans[i], (i + 1 == ans.size() ? '\n' : ' ')); if( ans.empty() ) puts(""); }
曾经想过字符串 hash,由于这个 hash 直接就是康托展开算。 发现这个题 hash 并不能前缀和相减,动态维护感受还要写平衡树,因而放弃了。 毕竟相对于平衡树你们仍是喜欢代码简短的树状数组吧。