题目地址:
https://www.acwing.com/problem/content/1006/
一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。在大会的晚餐上,调酒师Rainbow调制了 n n n杯鸡尾酒。这 n n n杯鸡尾酒排成一行,其中第 i i i杯酒 ( 1 ≤ i ≤ n ) (1≤i≤n) (1≤i≤n)被贴上了一个标签 s i s_i si,每个标签都是 26 个小写英文字母之一。设 s t r ( l , r ) str(l,r) str(l,r)表示第 l l l杯酒到第 r r r杯酒的 r − l + 1 r−l+1 r−l+1个标签顺次连接构成的字符串。若 s t r ( p , p 0 ) = s t r ( q , q 0 ) str(p,p_0)=str(q,q_0) str(p,p0)=str(q,q0),其中 1 ≤ p ≤ p 0 ≤ n , 1 ≤ q ≤ q 0 ≤ n , p ≠ q , p 0 − p + 1 = q 0 − q + 1 = r 1≤p≤p_0≤n, 1≤q≤q_0≤n, p≠q, p_0−p+1=q_0−q+1=r 1≤p≤p0≤n,1≤q≤q0≤n,p=q,p0−p+1=q0−q+1=r,则称第 p p p杯酒与第 q q q杯酒是“ r r r相似”的。当然两杯“ r r r相似” ( r > 1 ) (r>1) (r>1)的酒同时也是“ 1 1 1相似”、“ 2 2 2相似”、……、“ ( r − 1 ) (r−1) (r−1)相似”的。特别地,对于任意的 1 ≤ p , q ≤ n , p ≠ q 1≤p,q≤n,p≠q 1≤p,q≤n,p=q,第 p p p杯酒和第 q q q杯酒都是“ 0 0 0相似”的。在品尝环节上,品酒师Freda轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 i i i杯酒 ( 1 ≤ i ≤ n ) (1≤i≤n) (1≤i≤n)的美味度为 a i a_i ai。现在Rainbow公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 p p p杯酒与第 q q q杯酒调兑在一起,将得到一杯美味度为 a p × a q a_p×a_q ap×aq的酒。现在请各位品酒师分别对于 r = 0 , 1 , 2 , ⋯ , n − 1 r=0,1,2,⋯,n−1 r=0,1,2,⋯,n−1,统计出有多少种方法可以选出 2 2 2杯“ r r r相似”的酒,并回答选择 2 2 2杯“ r r r相似”的酒调兑可以得到的美味度的最大值。
输入格式:
第
1
1
1行包含
1
1
1个正整数
n
n
n,表示鸡尾酒的杯数。
第
2
2
2行包含一个长度为
n
n
n的字符串
S
S
S,其中第
i
i
i个字符表示第
i
i
i杯酒的标签。
第
3
3
3行包含
n
n
n个整数,相邻整数之间用单个空格隔开,其中第
i
i
i个整数表示第
i
i
i杯酒的美味度
a
i
a_i
ai。
输出格式:
输出共包括
n
n
n行。
第
i
i
i行输出
2
2
2个整数,中间用单个空格隔开。
第
1
1
1个整数表示选出两杯“
(
i
−
1
)
(i−1)
(i−1)相似”的酒的方案数,第
2
2
2个整数表示选出两杯“
(
i
−
1
)
(i−1)
(i−1)相似”的酒调兑可以得到的最大美味度。若不存在两杯“
(
i
−
1
)
(i−1)
(i−1)相似”的酒,这两个数均为
0
0
0。
数据范围:
先求后缀数组和高度数组 h h h,考虑 r r r相似的情形。将所有后缀按字典序排好序,设 t [ i ] t[i] t[i]是排名第 i i i的后缀,那么如果 h [ i ] < r h[i]<r h[i]<r,意味着 l c p ( t [ i − 1 ] , t [ i ] ) < r lcp(t[i-1],t[i])<r lcp(t[i−1],t[i])<r,所以如果 u ≤ i − 1 < i ≤ v u\le i-1<i\le v u≤i−1<i≤v,则根据 l c p lcp lcp性质,有 l c p ( t [ u ] , t [ v ] ) < r lcp(t[u],t[v])<r lcp(t[u],t[v])<r,所以每次发现 h [ i ] < r h[i]<r h[i]<r的时候,就可以将所有后缀分成若干组,不同组之间的 l c p lcp lcp小于 r r r,组内大于等于 r r r,那么有多少组 r r r相似的数对这个问题就容易解决了,接下来考虑如何求最大乘积。为了方便,我们从大到小来枚举 r r r,当 r = n r=n r=n的时候,显然 ∀ h [ i ] < r \forall h[i]<r ∀h[i]<r,也就是说此时是分为了 n n n组,每组一个后缀,在枚举到 r = n − 1 r=n-1 r=n−1的时候,就将 h [ i ] = n − 1 h[i]=n-1 h[i]=n−1的那些 ( i − 1 , i ) (i-1, i) (i−1,i)给合并起来(此处可以用并查集),合并完了之后,就达到了组内的 l c p lcp lcp大于等于 n − 1 n-1 n−1,组间小于 n − 1 n-1 n−1的效果,即组内一定是 n − 1 n-1 n−1相似的,组间不 n − 1 n-1 n−1相似。为了每次合并的时候,能方便求出最大 a p × a q a_p×a_q ap×aq,我们维护每个组的最大、次大、最小、次小值(这是因为可能 ∃ a k < 0 \exists a_k<0 ∃ak<0),合并之后同时更新这四个数,这是很容易的;而求组内的最大乘积,只需要枚举一下最大乘以次大,最小乘以次小,因为最大值只可能是这两种情况。代码如下:
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
using PLL = pair<long, long>;
const int N = 3e5 + 10;
const long INF = 2e18;
int n, m;
char s[N];
int sa[N], rk[N], y[N], c[N], he[N];
int w[N], p[N], sz[N];
long max1[N], max2[N], min1[N], min2[N];
vector<int> hs[N];
PLL res[N];
// 后缀数组模板
void get_sa() {
for (int i = 1; i <= n; i++) c[rk[i] = s[i]]++;
for (int i = 2; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[rk[i]]--] = i;
for (int k = 1;; k <<= 1) {
int num = 0;
for (int i = n - k + 1; i <= n; i++) y[++num] = i;
for (int i = 1; i <= n; i++) if (sa[i] > k) y[++num] = sa[i] - k;
for (int i = 1; i <= m; i++) c[i] = 0;
for (int i = 1; i <= n; i++) c[rk[i]]++;
for (int i = 2; i <= m; i++) c[i] += c[i - 1];
for (int i = n; i; i--) sa[c[rk[y[i]]]--] = y[i];
swap(rk, y);
rk[sa[1]] = num = 1;
for (int i = 2; i <= n; i++)
rk[sa[i]] = y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k] ? num : ++num;
if (num == n) break;
m = num;
}
}
// 高度数组模板
void get_height() {
for (int i = 1, k = 0; i <= n; i++) {
if (rk[i] == 1) continue;
if (k) k--;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
he[rk[i]] = k;
}
}
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 求size为x的组内的r相似数对的个数
long get(int x) {
return x * (x - 1l) / 2;
}
// 求r相似的数对个数以及最大乘积
PLL cal(int r) {
// cnt和maxv的更新依赖于上一轮计算的值,所以用static。
// 这里依赖的原因是,如果r相似一定有r - 1相似
static long cnt = 0, maxv = -INF;
for (auto x : hs[r]) {
int a = find(x - 1), b = find(x);
cnt -= get(sz[a]) + get(sz[b]);
p[a] = b;
sz[b] += sz[a];
cnt += get(sz[b]);
if (max1[a] >= max1[b]) {
max2[b] = max(max1[b], max2[a]);
max1[b] = max1[a];
} else if (max1[a] > max2[b]) max2[b] = max1[a];
if (min1[a] <= min1[b]) {
min2[b] = min(min1[b], min2[a]);
min1[b] = min1[a];
} else if (min1[a] < min2[b]) min2[b] = min1[a];
maxv = max(maxv, max(max1[b] * max2[b], min1[b] * min2[b]));
}
if (maxv == -INF) return {cnt, 0};
else return {cnt, maxv};
}
int main() {
scanf("%d", &n), m = 'z';
scanf("%s", s + 1);
for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
get_sa();
get_height();
for (int i = 2; i <= n; i++) hs[he[i]].push_back(i);
for (int i = 1; i <= n; i++) {
p[i] = i;
sz[i] = 1;
max1[i] = min1[i] = w[sa[i]];
max2[i] = -INF, min2[i] = INF;
}
for (int i = n - 1; i >= 0; i--) res[i] = cal(i);
for (int i = 0; i < n; i++) printf("%ld %ld\n", res[i].first, res[i].second);
}
时间复杂度 O ( n log n ) O(n\log n) O(nlogn),空间 O ( n ) O(n) O(n)。