P2178 [NOI2015] 品酒大会

题目链接

题意简述

有一个长度为 \(n\) 的字符串 \(S\),其中第 \(i\) 个字符对应的权值为 \(a_i\)

\(str(l,r)\) 表示 \(S\) 中第 \(l\) 个字符到第 \(r\) 个字符构成的字符串,我们定义第 \(p\) 个字符和第 \(q\) 个字符相似度为 \(r\) 当且仅当 \(str(p,p + r - 1) = str(q,q + r - 1)\),且 \(p \not = q\)。注意,对于同一对位置上的字符 \(p,q\),其相似度可以有多个,也就是说如果其最大的相似度为 \(r\),我们可以认为 \(p,q\) 的相似度为 \([0,r]\) 中的任意一整数。特别地,我们认为任意一个字符对 \(p,q\),在满足 \(p \not = q\) 的前提下,其相似度都可以为 \(0\)

现在的问题就是,请你求出相似度为 \(0,1,2,…,n - 1\) 的字符对的数量,以及相似度为 \(0,1,2,…,n - 1\) 的所有字符对的权值的最大值,如果不存在相似度为 \(r\) 的字符对,其最大值为 \(0\)。字符对 \(p,q\) 的权值的定义为 \(a_p \times a_q\)

数据范围与约定:\(n \leq 3 \times 10^5, a \in [-10^9,10^9]\)\(S\) 仅由小写字母构成。

解题思路

思路有点像我之前写的一篇题解:P6503 [COCI2010-2011#3] DIFERENCIJA

先对 \(S\) 进行后缀排序,求出其 \(height\) 数组,对于任意的 \(p,q(p \not = q)\),满足 \(rk[p] < rk[q]\),那么 \(p,q\) 相似度的最大值 \(r = \min_{i = rk[p] + 1}^{rk[q]} height[i]\)。但是直接这样做的复杂度是 \(O(n^2)\),显然过不了,于是我们尝试对其进行优化。

由上可知,\(p,q\)\(r\) 值的最大值取决于其在 \(height\) 上对应的区间的最小值,所以我们可以尝试在 \(height\) 上统计答案。

我们从区间 \([1,n]\) 开始,对于当前的区间 \([l,r]\),我们找到 \(height\)\([l + 1,r]\) 上的最小值的位置 \(k\),那么对于每一个字符对 \(p,q\),若满足 \(p \in [l,k),q \in[k,r]\),其相似度的最大值 \(r = height[k]\)。所以对于问题一,设 \(ans_i\) 表示相似度为 \(i\) 的字符对的数量,其对 \(ans_0,…,ans_r\) 的贡献就是 \((k - l) \times (r - k + 1)\),这里可以用前缀和来统计。处理完区间 \([l,r]\) 后,再对 \([l,k - 1]\)\([k,r]\) 区间进行相同方式的处理,如此递归下去,直到 \(l \geq r\) 后退出即可。

接下来考虑第二问,我们可以沿用第一问的方法来更新答案。对于当前区间 \([l,r]\) 以及最小的 \(height\) 值的位置 \(k\),我们只需要求出 \([l,k)\) 区间中的点对应的权值和 \([k,r]\) 区间中的点对应的权值的乘积的最大值即可,注意由于进行了后缀排序,所以要重新对应每个点对应的权值。由于 \(a\) 的值可以为负数,所以我们要分别求出 \([l,k)\)\([k,r]\) 区间的权值最小值、权值最大值以及权值的绝对值的最小值,然后把它们分别相乘取 \(\max\) 即可,更新答案的时候就是更新 \(0\)\(height[k]\) 的答案。

具体实现可以看代码。

代码实现

点击查看代码
#include<bits/stdc++.h>

using namespace std;
const int MN = 3e5;

int N,A[MN + 5];
char S[MN + 5];

int SA[MN + 5],RK[MN + 5];
int CNT[MN + 5],HP1[MN + 2 << 1],HP2[MN + 2 << 1];
int HG[MN + 5],B[MN + 5];

class Spare_Table{
private:
  int peak[MN + 5][20];
  function<int(int,int)> mxn;
  int Log2(int num){
    int res = -1;
    while(num > 0)
      num >>= 1,++res;
    return res;
  }
public:
  void Init(int *num,int len,const function<int(int,int)> &cmp){
    for(int i = 1;i <= len;++i)
      peak[i][0] = num[i];
    mxn = cmp;
    for(int k = 1;k < 20;++k)
      for(int i = len - (1 << k) + 1;i > 0;--i)
	peak[i][k] = mxn(peak[i][k - 1],peak[i + (1 << k - 1)][k - 1]);
    return;
  }
  int Peak(int l,int r){
    int lg2 = Log2(r - l + 1);
    return mxn(peak[l][lg2],peak[r - (1 << lg2) + 1][lg2]);
  }
}ST1,ST2[3];//最小\最大\绝对值最小

long long ANS1[MN + 5],ANS2[MN + 5];

void Get_ANS(int l,int r);

int main(){
  scanf("%d%s",&N,S + 1);
  for(int i = 1;i <= N;++i)
    scanf("%d",&A[i]);
  
  int base = 128,*x = HP1,*y = HP2;
  for(int i = 1;i <= N;++i)
    ++CNT[(x[i] = S[i])];
  for(int i = 1;i <= base;++i)
    CNT[i] += CNT[i - 1];
  for(int i = N;i > 0;--i)
    SA[CNT[x[i]]--] = i;
  for(int k = 1;k <= N;k <<= 1){
    int rk = 0;
    for(int i = N - k + 1;i <= N;++i)
      y[++rk] = i;
    for(int i = 1;i <= N;++i)
      if(SA[i] > k)
	y[++rk] = SA[i] - k;
    memset(CNT,0,sizeof(CNT));
    for(int i = 1;i <= N;++i)
      ++CNT[x[i]];
    for(int i = 1;i <= base;++i)
      CNT[i] += CNT[i - 1];
    for(int i = N;i > 0;--i)
      SA[CNT[x[y[i]]]--] = y[i];
    base = 1,swap(x,y);
    for(int i = 1;i <= N;++i){
      x[SA[i]] = base;
      if(y[SA[i]] != y[SA[i + 1]] || y[SA[i] + k] != y[SA[i + 1] + k])
	++base;
    }
    if(base > N)
      break;
  }
  for(int i = 1;i <= N;++i)
    RK[SA[i]] = i,B[i] = A[SA[i]];
  for(int i = 1,k = 0;i <= N;++i){
    if(k > 0)
      --k;
    int other = SA[RK[i] - 1];
    while(S[i + k] == S[other + k])
      ++k;
    HG[RK[i]] = k;
  }
  
  for(int i = 1;i <= N;++i)
    HP1[i] = i,ANS2[i] = -2e18;
  ST1.Init(HP1,N,[&](int a,int b) -> int {return HG[a] < HG[b] ? a : b;});
  ST2[0].Init(B,N,[&](int a,int b) -> int {return a < b ? a : b;});
  ST2[1].Init(B,N,[&](int a,int b) -> int {return a > b ? a : b;});
  ST2[2].Init(B,N,[&](int a,int b) -> int {return abs(a) < abs(b) ? a : b;});
  Get_ANS(1,N);
  
  for(int i = N - 1;i >= 0;--i){
    ANS1[i] += ANS1[i + 1];
    ANS2[i] = max(ANS2[i],ANS2[i + 1]);
  }
  for(int i = 0;i < N;++i)
    printf("%lld %lld\n",ANS1[i],ANS2[i] *= (ANS1[i] != 0));
  return 0;
}

void Get_ANS(int l,int r){
  if(l >= r)
    return;
  int mid = ST1.Peak(l + 1,r);
  ANS1[HG[mid]] += (long long)(mid - l) * (r - mid + 1);
  for(int i = 0;i < 3;++i)
    ANS2[HG[mid]] = max(ANS2[HG[mid]],(long long)ST2[i].Peak(l,mid - 1) * ST2[i].Peak(mid,r));
  Get_ANS(l,mid - 1),Get_ANS(mid,r);
  return;
}
posted @ 2023-10-31 08:22  静观默察  阅读(14)  评论(0)    收藏  举报