【CF1335E】 Three Blocks Palindrome 题解

首先,发现 E1 的数据范围很小,我们可以考虑这样一个算法:

  • 从前往后枚举第一个位置 $i$,从后往前枚举第二个位置 $j$,同时保证 $0 le i<j le n+1$。
  • 枚举取值 $k=1 sim 26$,对于每个 $k$ 分别找到区间 $[1,i], [j,n]$ 中 $k$ 的出现次数,将二者取较小值得到 $ans_1$。
  • 中间的部分,找到区间 $[i+1,j-1]$ 中出现次数最多的数,并将其出现次数即为 $ans_2$,最终答案即为 $2 imes ans_1+ans_2$。

当然直接暴力是过不去的,我们可以将数字 $1 sim 26$ 在序列每一个位置的出现次数都记录一个前缀和,这样在查询某个字符在某个区间的出现次数时可以直接 $O(1)$ 计算得到。

该算法的时间复杂度为 $O(n^2 imes a_i)$,3s 内肯定能过。


接下来考虑如何优化这个算法。

优化一

我们发现,在枚举 $i$ 的过程中,当 $j$ 一定时,相比于 $i-1$ 只有 $a_i$ 的前缀出现次数发生了改变。

所以不必枚举取值,每次枚举 $i,j$ 时直接令 $k=a_i$ 即可,时间复杂度降为 $O(n^2)$。

优化二

根据优化一,对于枚举的的每一个 $i$,都最多只有一个位置 $j$ 同时满足以下两个条件:

  • $a_i=a_j$
  • $sum_{x=1}^{i-1}[a_x=a_i]=sum_{x=j+1}^n[a_x=a_i]$

因此可以针对不同的取值 $k$,可以记录序列中第 $1,2,...$ 个数值等于 $k$ 的位置。

在每次枚举 $i$ 的时候,合适的 $j$ 就可以直接通过记录被直接找到。

然后中间的部分只需要枚举取值 $k$,通过前缀和找到 $[i+1,j-1]$ 中出现次数最多的 $k$,该算法的时间复杂度就降为了 $O(n imes max{a_i})$。

此时已经可以通过本题。


节省空间的解法

前文提到的前缀和数组,理论上开 $4e7$ 的 int 是可以满足空间限制的。

但是我在做题的时候把空间算错了,就以为上述算法无法通过,于是又想了一个节省空间,但效率稍低的算法。

既然没法记前缀和了,我们可以用一些其它的方式求区间和。

我们开 200 个 vector(下文定义变量名为 `v`),第 $i$ 个 vector 按从前到后的顺序记录原序列中数值等于 $i$ 的位置

  • 按 $n ightarrow1$ 逐个枚举 $i$,这样做是为了方便之后的查找数列单调不降,方便之后使用 STL 的二分(当然手写二分也行)。
  • 记录 $a_i=k$,二分找 $k$ 是整个序列中第几个等于 $k$ 的数,那么就知道 $[i,n]$ 有不同的 $k$ 的数量,在此记为 $num$。
  • 同理,可以找到一个满足 $sum_{x=1}^{j}[a_x=a_i]=sum_{x=j}^n[a_x=a_i]$ 的 $j$,这可以查找 `v[a[i]][num - 1]` 得到。
  • 接下来查找中间的部分 $[j+1,i-1]$。
  • 枚举在 $1sim200$ 内的取值 $k$。
  • 找到所有等于 $k$ 的数中第一个大于 $i-1$ 的数在 vector 中的下标 $pos1$。
  • 找到所有等于 $k$ 的数中第一个大于等于 $j+1$ 的数在 vector 中的下标 $pos2$。
  • $pos1-pos2$ 就是区间 $[j+1,i-1]$ 中等于 $k$ 的数字数量。
  • 对于相同的 $i,j$ 找到最大的一个 $pos1-pos2$ 加上 $2num$ 即为答案。

由于两百个 vector 一共就存 $n$ 个数,该算法空间复杂度降为 $O(n)$。

当然,该算法对时间作出了一定牺牲。瓶颈在于查找 $pos1,pos2$ 相比原算法多了一个二分,但两百次 vector 二分的长度之和等于 $n$,所以算法的时间复杂度为 $O(n imes max{a_i} imes k)$,$k$ 最大约等于 $10$(不含 vector 自身常数)。

该做法不经过任何常数优化也可以通过,详情见代码。

代码(最慢点 1.67s,空间最大 3.35MB):

int t, n, a[200010], q[210], h[210], ans;
vector <int> v[210];
 
inline int max(int a, int b){
    return a > b ? a : b;
}
 
int main(){
 
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= 200; i++)
            q[i] = h[i] = 0, v[i].clear();
        ans = 0;
        for(int i = 1; i <= n; i++)
            v[a[i]].push_back(i), q[a[i]]++, ans = max(ans, q[a[i]]);
        for(int i = n, pos, now, poss; i >= 1; i--){
            poss = lower_bound(v[a[i]].begin(), v[a[i]].end(), i) - v[a[i]].begin();
            poss = v[a[i]].size() - poss;
            pos = v[a[i]][poss - 1];
            if(pos >= i) continue;
            now = 0;
            for(int j = 1, pos1, pos2; j <= 200; j++){
                pos1 = lower_bound(v[j].begin(), v[j].end(), i) - v[j].begin();
                pos2 = upper_bound(v[j].begin(), v[j].end(), pos) - v[j].begin();
                now = max(now, pos1 - pos2);
            }
            ans = max(ans, poss * 2 + now);
        }
        printf("%d
", ans);
    }
 
    return 0;
}
原文地址:https://www.cnblogs.com/zengpeichen/p/14453971.html