【OI】hash在字元匹配的妙用[Jsoi2016]扭動的迴文串

傳送門:[Jsoi2016]扭動的迴文串

題意:給出兩個字元串A串和B串,可以將A串的[i,k]切出來,和B串的[k,j]拼在一起,組成新的字元串,求最長的A串內或B串內或經過以上操作的字元串內的最長迴文串

數學語言:對於切割的字元串,求 Max_{i leq k leq j}; j - i + 2; (;check(A[i,k]+B[k,j]) == 1;) (check是判斷是否為迴文串的bool函數)

數據範圍: n leq 10 ^ 5

這道題時間複雜度Theta (n^2) 的演算法應該是很顯然的,用manachar先掃一遍A串和B串記錄ans然後枚舉切割點每一次做一遍manachar即可。聽說有很可觀的暴力分(貌似能A(霧))

然而蒟蒻題主只會這種暴力哎

然後就看了看WerKeyTom_FTD的blog,不愧是棟爺我剛開始真的是看不懂qwq,不過後來偶然翻到了劉汝佳的藍書3.4.3(如果你和我版本一樣應該是P224頁)基於hash的LCP演算法,我才發現原來棟爺用的就是hash加二分答案(不過他好像寫了只是我hash太菜沒看懂)。

所以我就來講一下這種判法,看看有沒有hash跟我一樣菜的不知道這種辦法。(大概沒有qwq)

對於一個字元串的hash,我想你們應該也是知道的,就比如說找一段文章中有哪個單詞出現了最多次,你就可以把字元串每一位hash掉,具體hash值的演算法應該就是 sum_{i=1}^{len} Word[i]*p^i \% mod ,這裡的p一般取的是字母的種類數,就像十進位存數一樣,如果是嚴格的26個字母,你可以讓 p = 26 ,這樣就能避免不同的位數互相影響。

寫成代碼大概就是醬紫的:

for (int i = 1; i <= len; i++) c[i] = c[i] - A;//將字母壓成0~25的數字int sum = 0;for (int i = 1; i <= len; i++) sum = (sum + c[i] * p) % mod;

可是我們要求的是一段區間的字母的hash值呀,難道你能將hash值按前綴和操作?

其實還真的可以。不過後綴和會更好操作哎。

對於一個到 1 sim i 的字元串,記hash值為 H(i) ,則容易寫出 H(i) 的表達式:

H(i) = sum_{j = 1} ^ {i} c[j]·p ^ {i-j} \% mod

那麼也容易得到 H(i - 1) 的值

H(i - 1) = sum_{j = 1} ^ {i - 1} c[j]·p ^ {i-j-1} \% mod

兩式相減: H(i) - H(i - 1)	imes p = c[i] \% mod

右邊即是c[i]的hash值

類似的手法,我們很容易得到 H(i-L) 的的值,為了消掉無關項,我們要先將 H(i-L) 乘個 p^L ,使得 H(i-L) 內的 c[j]H(i) 的齊次:

H(i-L) 	imes p^L = sum_{j = 1} ^ {i - L} c[j]·p ^ {i-j} \% mod

接著兩式相減: H(i) - H(i - L) 	imes p^L= sum_{j = i - L + 1} ^ {i} c[j]·p ^ {i-j} \% mod

這便是一個獨屬於區間 [i-L+1, i] 的hash值啦!

我們可以通過預處理字元串的後綴和和預處理 p^L ,在每次判斷的時候寫一個check函數可以做到 Theta(1) 的效率,枚舉A串和B串的起始點,先在本串擴展,然後連接另一個串。具體的話。。。我來舉一個例子:

A串: ADACABA

B串: QWQBDAA

例如你在枚舉到A串的第四個字母C的時候,你會先訪問A的manachar結果,發現這個C本身是迴文子串ACA的中心,那麼更改現在子串為ACA(可以證明這是最優的,如果B串本身也能做到與A串匹配,但即便這樣也是與A串直接擴展等價的(想一想為什麼)),接著你就鎖定了A串第二個字元D和B串的第五個字元D,這裡因為我們hash判斷是 Theta(1) 的效率,我們不妨二分可以進行擴展的子串長度,對於每次擴展hash判斷,便使擴展的複雜度變成了 Theta(logn) 級別的,所以總複雜度為 Theta(nlogn)

#include<bits/stdc++.h>#define fo(i, a, b) for (R int i = (a); i <= (b); i++)#define fd(i, a, b) for (R int i = (a); i >= (b); i--)#define R register#define N 200105#define in inline//#define debug#define mod1 122777#define mod2 388211#define ll long longll nn, n, a[N], b[N], fa[N], fb[N], ans, ha[N][2] ,hb[N][2], mi[N][2];in char gchar (){ char ch = std::getchar(); while (ch >Z || ch < A) ch = std::getchar(); return ch; }in bool check (ll l1, ll r1, ll l2, ll r2){ ll x = (ha[r1][0] - ha[l1 - 1][0] * mi [r1 - l1 + 1][0] % mod1) % mod1; ll y = (hb[l2][0] - hb[r2 + 1][0] * mi [r2 - l2 + 1][0] % mod1) % mod1; x = (x + mod1) % mod1; y = (y + mod1) % mod1; if (x != y) return 0; x = (ha[r1][1] - ha[l1 - 1][1] * mi [r1 - l1 + 1][1] % mod2) % mod2; y = (hb[l2][1] - hb[r2 + 1][1] * mi [r2 - l2 + 1][1] % mod2) % mod2; x = (x + mod2) % mod2; y = (y + mod2) % mod2; return (x == y);}in ll solve (ll x, ll y){ ll l = 0, r = std::min(x, n - y + 1); while (l < r) { ll mid = (l + r + 1) >> 1; if (check(x - mid + 1, x, y, y + mid - 1)) l = mid; else r = mid - 1; } return l;}int main(){#ifdef debug freopen("miao.txt", "r", stdin);#endif scanf("%lld", &n); fo (i, 1, n) a[i << 1] = gchar() - A; fo (i, 1, n) b[i << 1] = gchar() - A; fo (i, 0, n) b[(i << 1) + 1] = a[(i << 1) + 1] = 27; a[0] = b[0] = 233; n = n << 1; a[n + 1] = b[n + 1] = 27; a[n + 2] = b[n + 2] = 2333; int j = 0; fo (i, 1, n) { if (fa[j] + j > i) fa[i] = std::min(fa[(j << 1) - i], j + fa[j] - i); while (a[i + fa[i]] == a[i - fa[i]]) fa[i]++; fa[i]--; if (fa[i] + i > fa[j] + j) j = i; } j = 0; fo (i, 1, n) { if (fb[j] + j > i) fb[i] = std::min(fb[(j << 1) - i], j + fb[j] - i); while (b[i + fb[i]] == b[i - fb[i]]) fb[i]++; fb[i]--; if (fb[i] + i > fb[j] + j) j = i; } fo (i, 1, n) ans = std::max(ans, std::max(fa[i], fb[i])); n = n >> 1; mi[0][0] = mi[0][1] = 1; fo (i, 1, n) { mi[i][0] = mi[i - 1][0] * 27 % mod1; mi[i][1] = mi[i - 1][1] * 27 % mod2; } ha[0][0] = ha[0][1] = 1; fo (i, 1, n) { ha[i][0] = (ha[i - 1][0] * 27 + a[i << 1]) % mod1; ha[i][1] = (ha[i - 1][1] * 27 + a[i << 1]) % mod2; } hb[n + 1][0] = hb[n + 1][1] = 1; fd (i, n, 1) { hb[i][0] = (hb[i + 1][0] * 27 + b[i << 1]) % mod1; hb[i][1] = (hb[i + 1][1] * 27 + b[i << 1]) % mod2; } ll l, r; nn = n << 1; fo (i, 2, nn) { l = i - fa[i]; r = i + fa[i]; l = l + 1 >> 1; r = r >> 1;#ifdef debug printf("l = %lld, r = %lld
", l, r);#endif ans = std::max(ans, fa[i] + (solve(l - 1, r) << 1)); } fo (i, 2, nn) { l = i - fb[i]; r = i + fb[i]; l = l + 1 >> 1; r = r >> 1; ans = std::max(ans, fb[i] + (solve(l, r + 1) << 1)); } printf("%lld", ans); return 0;}

然而題主太懶了懶得打注釋。。。其實難點主要我覺得是二分答案的細節太多了,注意一下各種邊界的處理多調幾次就行。

本文為蒟蒻所作,若有語言欠妥之處,請在評論區指出,謝謝。

若要轉載請註明出處。


推薦閱讀:

基礎知識複習

TAG:OI信息學奧林匹克 | 信息學競賽 | 哈希函數 |