對於整數序列 \((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 並不能前綴和相減,動態維護感受還要寫平衡樹,因而放棄了。 畢竟相對於平衡樹你們仍是喜歡代碼簡短的樹狀數組吧。