1. 程式人生 > >BZOJ4566 [Haoi2016]找相同字符 【後綴數組】

BZOJ4566 [Haoi2016]找相同字符 【後綴數組】

down 過去 return div cnblogs rank mes scan 之間

題目

給定兩個字符串,求出在兩個字符串中各取出一個子串使得這兩個子串相同的方案數。兩個方案不同當且僅當這兩
個子串中有一個位置不同。

輸入格式

兩行,兩個字符串s1,s2,長度分別為n1,n2。1 <=n1, n2<= 200000,字符串中只有小寫字母

輸出格式

輸出一個整數表示答案

輸入樣例

aabb

bbaa

輸出樣例

10

題解

先考慮暴力怎麽做
我們枚舉兩個串的各自一個後綴suffix(i)和suffix(j)
則他們對答案的貢獻是LCP(suffix(i),suffix(j))
如此得到一個\(O(n^3)\)的算法

當然如果你知道後綴數組,可以\(O(1)\)求LCP,可以優化到\(O(n^2)\)

當然如果你知道後綴數組的套路,用一個單調棧掃一遍height[]可以做到\(O(nlogn)\)【主要復雜度在求後綴數組】
具體這樣:
我們知道,兩個後綴之間的LCP是他們之間height的最小值
如果給出一個位置的後綴,想求這個位置之前所有的後綴與這個位置的LCP之和之類的東西,由於求最小值是一路過去的,所以前面的後綴的LCP不會比後面的大,所以整體是單調不下降的,可以用單調棧處理

最後我們只需要分A、B串各用單調棧掃兩次統計出答案就可以了

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm> #define LL long long int #define REP(i,n) for (int i = 1; i <= (n); i++) #define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt) #define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<‘ ‘; puts(""); using namespace std; const int maxn = 400005,maxm = 100005
,INF = 1000000000; inline int read(){ int out = 0,flag = 1; char c = getchar(); while (c < 48 || c > 57) {if (c == ‘-‘) flag = -1; c = getchar();} while (c >= 48 && c <= 57) {out = (out << 3) + (out << 1) + c - ‘0‘; c = getchar();} return out * flag; } int sa[maxn],rank[maxn],height[maxn],t1[maxn],t2[maxn],bac[maxn],n,m; char s[maxn]; int len; LL ans; void getSA(){ int *x = t1,*y = t2; m = 256; for (int i = 0; i <= m; i++) bac[i] = 0; for (int i = 1; i <= n; i++) bac[x[i] = s[i]]++; for (int i = 1; i <= m; i++) bac[i] += bac[i - 1]; for (int i = n; i; i--) sa[bac[x[i]]--] = i; for (int k = 1; k <= n; k <<= 1){ int p = 0; for (int i = n - k + 1; i <= n; i++) y[++p] = i; for (int i = 1; i <= n; i++) if (sa[i] - k > 0) y[++p] = sa[i] - k; for (int i = 0; i <= m; i++) bac[i] = 0; for (int i = 1; i <= n; i++) bac[x[y[i]]]++; for (int i = 1; i <= m; i++) bac[i] += bac[i - 1]; for (int i = n; i; i--) sa[bac[x[y[i]]]--] = y[i]; swap(x,y); p = x[sa[1]] = 1; for (int i = 2; i <= n; i++) x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? p : ++p); if (p >= n) break; m = p; } for (int i = 1; i <= n; i++) rank[sa[i]] = i; for (int i = 1,k = 0; i <= n; i++){ if (k) k--; int j = sa[rank[i] - 1]; while (s[i + k] == s[j + k]) k++; height[rank[i]] = k; } } int st[maxn],cnt[maxn],top; LL cal[maxn]; void solve(){ for (int i = 1; i <= n; i++){ if (!height[i]) {top = 0; continue;} int tot = sa[i - 1] <= len ? 1 : 0; while (top && st[top] > height[i]) tot += cnt[top--]; if (tot) st[++top] = height[i],cnt[top] = tot; cal[top] = cal[top - 1] + (LL)st[top] * (LL)cnt[top]; if (sa[i] > len) ans += cal[top]; } top = 0; for (int i = 1; i <= n; i++){ if (!height[i]) {top = 0; continue;} int tot = sa[i - 1] > len ? 1 : 0; while (top && st[top] > height[i]) tot += cnt[top--]; if (tot) st[++top] = height[i],cnt[top] = tot; cal[top] = cal[top - 1] + (LL)st[top] * (LL)cnt[top]; if (sa[i] <= len) ans += cal[top]; } } int main(){ scanf("%s",s + 1); len = strlen(s + 1); s[len + 1] = ‘#‘; scanf("%s",s + len + 2); n = strlen(s + 1); getSA(); solve(); cout << ans << endl; return 0; }

BZOJ4566 [Haoi2016]找相同字符 【後綴數組】