【ACWing】1004. 品酒大会

题目地址:

https://www.acwing.com/problem/content/1006/

一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。在大会的晚餐上,调酒师Rainbow调制了 n n n杯鸡尾酒。这 n n n杯鸡尾酒排成一行,其中第 i i i杯酒 ( 1 ≤ i ≤ n ) (1≤i≤n) (1in)被贴上了一个标签 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 rl+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 1pp0n,1qq0n,p=q,p0p+1=q0q+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) (r1)相似”的。特别地,对于任意的 1 ≤ p , q ≤ n , p ≠ q 1≤p,q≤n,p≠q 1p,qn,p=q,第 p p p杯酒和第 q q q杯酒都是“ 0 0 0相似”的。在品尝环节上,品酒师Freda轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 i i i杯酒 ( 1 ≤ i ≤ n ) (1≤i≤n) (1in)的美味度为 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,,n1,统计出有多少种方法可以选出 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) (i1)相似”的酒的方案数,第 2 2 2个整数表示选出两杯“ ( i − 1 ) (i−1) (i1)相似”的酒调兑可以得到的最大美味度。若不存在两杯“ ( i − 1 ) (i−1) (i1)相似”的酒,这两个数均为 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[i1],t[i])<r,所以如果 u ≤ i − 1 < i ≤ v u\le i-1<i\le v ui1<iv,则根据 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=n1的时候,就将 h [ i ] = n − 1 h[i]=n-1 h[i]=n1的那些 ( i − 1 , i ) (i-1, i) (i1,i)给合并起来(此处可以用并查集),合并完了之后,就达到了组内的 l c p lcp lcp大于等于 n − 1 n-1 n1,组间小于 n − 1 n-1 n1的效果,即组内一定是 n − 1 n-1 n1相似的,组间不 n − 1 n-1 n1相似。为了每次合并的时候,能方便求出最大 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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值