@loj - 2507@ 「CEOI2011」Matching


@description@

對於整數序列 \((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

@solution@

先對問題做一步轉化:求 {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) 的。

@accepted code@

#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("");
}

@details@

曾經想過字符串 hash,由於這個 hash 直接就是康託展開算。 發現這個題 hash 並不能前綴和相減,動態維護感受還要寫平衡樹,因而放棄了。 畢竟相對於平衡樹你們仍是喜歡代碼簡短的樹狀數組吧。

相關文章
相關標籤/搜索