題面
[NOI2015] 品酒大會
題目描述
一年一度的“幻影閣夏日品酒大會”隆重開幕了。大會包含品嚐和趣味挑戰 兩個環節,分別向優勝者頒發“首席品酒家”和“首席獵手”兩個獎項,吸引了眾多品酒師參加。
在大會的晚餐上,調酒師 Rainbow 調製了 \(n\) 杯雞尾酒。這 \(n\) 杯雞尾酒排成一行,其中第 \(n\) 杯酒 (\(1 ≤ i ≤ n\)) 被貼上了一個標籤 \(s_i\) ,每個標籤都是 \(26\) 個小寫 英文字母之一。設 \(str(l, r)\) 表示第 \(l\) 杯酒到第 \(r\) 杯酒的 \(r - l + 1\) 個標籤順次連線構成的字串。若 \(str(p, p_0) = str(q, q_0)\),其中 \(1 ≤ p ≤ p_0 ≤ n\), \(1 ≤ q ≤ q_0 ≤ n\), \(p ≠ q\),\(p_0-p+1 = q_0 - q + 1 = r\) ,則稱第 \(p\) 杯酒與第 \(q\) 杯酒是“ \(r\) 相似” 的。當然兩杯“ \(r\) 相似”(\(r > 1\))的酒同時也是“ \(1\) 相似”、“ \(2\) 相似”、……、“ \((r - 1)\) 相似”的。特別地,對於任意的 \(1 ≤ p ,q ≤ n,p ≠ q\),第 \(p\) 杯酒和第 \(q\) 杯酒都 是“ \(0\) 相似”的。
在品嚐環節上,品酒師 Freda 輕鬆地評定了每一杯酒的美味度,憑藉其專業的水準和經驗成功奪取了“首席品酒家”的稱號,其中第 \(i\) 杯酒 (\(1 ≤ i ≤ n\)) 的 美味度為 \(a_i\) 。現在 Rainbow 公佈了挑戰環節的問題:本次大會調製的雞尾酒有一個特點,如果把第 \(p\) 杯酒與第 \(q\) 杯酒調兌在一起,將得到一杯美味度為 \(a_p\times a_q\) 的 酒。現在請各位品酒師分別對於 \(r = 0,1,2,⋯,n-1\) ,統計出有多少種方法可以 選出 \(2\) 杯“ \(r\) 相似”的酒,並回答選擇 \(2\) 杯“\(r\) 相似”的酒調兌可以得到的美味度的最大值。
輸入格式
第 \(1\) 行包含 \(1\) 個正整數 \(n\) ,表示雞尾酒的杯數。
第 \(2\) 行包含一個長度為 \(n\) 的字串 \(S\),其中第 \(i\) 個字元表示第 \(i\) 杯酒的標籤。
第 \(3\) 行包含 \(n\) 個整數,相鄰整數之間用單個空格隔開,其中第 \(i\) 個整數表示第 \(i\) 杯酒的美味度 \(a_i\) 。
輸出格式
包括 \(n\) 行。
第 \(i\) 行輸出 \(2\) 個整數,中間用單個空格隔開。第 \(1\) 個整 數表示選出兩杯“ \((i - 1)\) 相似”的酒的方案數,第 2 個整數表示選出兩杯 “ \((i - 1)\) 相似”的酒調兌可以得到的最大美味度。若不存在兩杯“ \((i - 1)\) 相似” 的酒,這兩個數均為 \(0\) 。
樣例 #1
樣例輸入 #1
10
ponoiiipoi
2 1 4 7 4 8 3 6 4 7
樣例輸出 #1
45 56
10 56
3 32
0 0
0 0
0 0
0 0
0 0
0 0
0 0
樣例 #2
樣例輸入 #2
12
abaabaabaaba
1 -2 3 -4 5 -6 7 -8 9 -10 11 -12
樣例輸出 #2
66 120
34 120
15 55
12 40
9 27
7 16
5 7
3 -4
2 -4
1 -4
0 0
0 0
提示
【樣例說明 1】
用二元組 \((p, q)\) 表示第 \(p\) 杯酒與第 \(q\) 杯酒。
\(0\) 相似:所有 \(45\) 對二元組都是 \(0\) 相似的,美味度最大的是 $8 × 7 = 56 $。
\(1\) 相似: $(1,8) (2,4) (2,9) (4,9) (5,6) (5,7) (5,10) (6,7) (6,10) (7,10) $,最大的 \(8 × 7 = 56\) 。
\(2\) 相似: \((1,8) (4,9) (5,6)\) ,最大的 \(4 × 8 = 32\) 。
沒有 \(3,4,5, ⋯ ,9\) 相似的兩杯酒,故均輸出 \(0\) 。
【時限1s,記憶體512M】
簡要題意
-
長 \(n\) 的字串 \(s\)。
-
對於 \(r \in [0, n)\)
-
求 \(\forall 1\le i < j \le n \space lcp(i, j) \ge r\) 的對數。
-
以及 滿足 \(lcp(i, j) \ge r\) 時 \(a_i \times a_j\) 的最大值。
-
思想
Step 1
看到 \(LCP\) 想到 \(Suffix\space Array\)。
問題轉化成滿足 \(\min \limits_{x=sa_i + 1}^{sa_j} height_x \ge r\) 的答案。
Step 2
大於等於很難受,我們把他轉化成等於再做字尾和與最大值就行了。
問題轉化成滿足 \(\min \limits_{x=sa_i + 1}^{sa_j} height_x = r\) 的答案。
Step 3
要求一個區間內某最小值等於某值。
經典套路
一般使用笛卡爾樹或並查集。
這裡使用並查集。
從大到小列舉height,每次把左右兩個區間合併並計算答案。
Step 4
考慮合併的時候如何更新。
第一問的答案只有在橫跨了列舉的值時才會產生貢獻。
將左右區間的長度相乘即可。
第二問的答案就拿左邊的最大最小,右邊的最大最小乘,因為可能有負數。
Talk is cheap, show me the code
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 3e5 + 5;
constexpr long long INF = 0x3f3f3f3f3f3f3f3f;
int cnt[MAXN], oldrk[MAXN * 2], id[MAXN], key1[MAXN];
bool cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
}
struct SuffixArray {
int n, sa[MAXN], rk[MAXN], height[MAXN];
void init(string s) {
memset(cnt, 0, sizeof cnt);
memset(oldrk, 0, sizeof oldrk);
memset(id, 0, sizeof id);
memset(key1, 0, sizeof key1);
memset(sa, 0, sizeof sa);
memset(rk, 0, sizeof rk);
memset(height, 0, sizeof height);
n = int(s.size());
int m = 27, p;
s = " " + s;
for (int i = 1; i <= n; i++)
cnt[rk[i] = s[i] - 'a' + 1]++;
for (int i = 1; i <= m; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[rk[i]]--] = i;
for (int w = 1;; w <<= 1, m = p) {
p = 0;
for (int i = n; i > n - w; i--)
id[++p] = i;
for (int i = 1; i <= n; i++)
if (sa[i] > w)
id[++p] = sa[i] - w;
memset(cnt, 0, sizeof cnt);
for (int i = 1; i <= n; i++)
cnt[key1[i] = rk[id[i]]]++;
for (int i = 1; i <= m; i++)
cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; i--)
sa[cnt[key1[i]]--] = id[i];
memcpy(oldrk + 1, rk + 1, n * sizeof(int));
p = 0;
for (int i = 1; i <= n; i++) {
if (cmp(sa[i], sa[i - 1], w))
rk[sa[i]] = p;
else
rk[sa[i]] = ++p;
}
if (p == n)
break;
}
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 0)
continue;
if (k)
k--;
while (s[i + k] == s[sa[rk[i] - 1] + k])
k++;
height[rk[i]] = k;
}
}
bool ask(int l, int r) {
return height[l] > height[r];
}
} sa;
int n;
string s;
int a[MAXN], idx[MAXN];
long long res1[MAXN], res2[MAXN];
struct Union {
int n; // node sums
int p[MAXN]; // parent
long long ans[MAXN], mx[MAXN], mn[MAXN], sz[MAXN];
void init(int _n) {
n = _n;
for (int i = 1; i <= n; i++)
p[i] = i, sz[i] = 1, mx[i] = mn[i] = a[i], ans[i] = -INF;
}
int get(int x) {
if (x == p[x])
return x;
return p[x] = get(p[x]);
}
void merge(int x, int y, int len) {
x = get(x);
y = get(y);
p[y] = x;
res1[len] += 1ll * sz[x] * sz[y];
sz[x] += sz[y];
ans[x] = max({ ans[x], ans[y], 1ll * mx[x] * mx[y], 1ll * mx[x] * mn[y], 1ll * mn[x] * mx[y], 1ll * mn[x] * mn[y] });
mx[x] = max(mx[x], mx[y]);
mn[x] = min(mn[x], mn[y]);
res2[len] = max(res2[len], ans[x]);
}
} u;
bool cmp2(int x, int y) {
return sa.ask(x, y);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n >> s;
for (int i = 1; i <= n; i++)
cin >> a[i], idx[i] = i;
u.init(n);
sa.init(s);
sort(idx + 2, idx + n + 1, cmp2);
memset(res1, 0, sizeof res1);
memset(res2, 0xc0, sizeof res2);
for (int i = 2; i <= n; i++)
u.merge(sa.sa[idx[i]], sa.sa[idx[i] - 1], sa.height[idx[i]]);
for (int i = n - 1; i >= 0; i--)
res1[i] += res1[i + 1];
for (int i = n - 1; i >= 0; i--)
res2[i] = max(res2[i], res2[i + 1]);
for (int i = 0; i < n; i++) {
cout << res1[i] << " ";
if (res1[i] == 0)
cout << 0 << endl;
else
cout << res2[i] << endl;
}
return 0;
}
運用到的知識點&Trick
SA(知識點)
\(Suffix\space Array\) 中字尾 \(i\) 與 字尾 \(j\) 的 \(LCP\) 為 \(\min \limits_{x=sa_i + 1}^{sa_j} height_x\)。
並查集(Trick)
求滿足區間最小/最大值等於某數的答案。
從大往小/從小往大列舉最小/最大值並把左右區間合併計算答案。