atcoder ABC 452 题解

atcoder abc452 题解

几天不见 abc 更拉了~

A - Gothec

问题描述

下面这5个日期 被称为五季节(gosekku)

  • 1月7日
  • 3月3日
  • 5月5日
  • 7月7日
  • 9月9日

如果给定的月份 (M)、日期 (D) 是五节句之一,输出 Yes;否则输出 No

解题思路

直接用 pair 把所有五季节全部储存起来,输入完成之后直接遍历就行,或者可以一大堆 if-else 解决这个问题。

代码

cpp 复制代码
// 存储 5 个五节句:{月, 日}
pair<int, int> festival[5] = {{1,7}, {3,3}, {5,5}, {7,7}, {9,9}};

int main()
{
    int month, day;
    cin >> month >> day;         // 输入月份和日期
    pair<int, int> input = {month, day};

    // 遍历比对是否是节日
    for(int i=0; i<5; i++){
        if(input == festival[i]){
            cout << "Yes" << endl;
            return 0;
        }
    }
    cout << "No" << endl;
    return 0;
}

B - Draw Frame

问题描述

有一个 HHH 行 WWW 列 的网格,高桥准备将网格中的每个格子涂成黑色或白色。

规则:

  • 网格边框上的所有格子涂成黑色
  • 其余格子涂成白色 输出涂色后的网格。

形式化定义 对于第 iii 行(从上往下数,1≤i≤H1 \le i \le H1≤i≤H)、第 jjj 列(从左往右数,1≤j≤W1 \le j \le W1≤j≤W)的格子,记为格子 (i,j)(i,j)(i,j)。

  • 边相邻 :当且仅当 ∣i−k∣+∣j−l∣=1|i-k|+|j-l|=1∣i−k∣+∣j−l∣=1 时,称格子 (i,j)(i,j)(i,j) 和格子 (k,l)(k,l)(k,l) 边相邻。

  • 边框格子 :当且仅当一个格子的边相邻格子数量不超过 3 个时,称该格子为边框格子。

请输出 HHH 个字符串 S1,S2,...,SHS_1,S_2,\dots,S_HS1,S2,...,SH,满足:

  • SiS_iSi 长度为 WWW - 若格子 (i,j)(i,j)(i,j) 是边框格子 → 第 iii 个字符串的第 jjj 个字符为 #(黑色) - 否则 → 字符为 .(白色)

解题思路

直接枚举网格里面的每一个格子,如果发现是在边界上就输出 #,否则就是 .

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int n, m; // n=行数,m=列数

int main()
{
    cin >> n >> m; // 输入网格的行数和列数
    // 遍历每一行 i
    for (int i = 1; i <= n; i++, cout << '\n')
        // 遍历每一列 j
        for (int j = 1; j <= m; j++)
        {
            // 边框判断:第一行/最后一行/第一列/最后一列 → 输出 #
            if (i == 1 || i == n || j == 1 || j == m)
                cout << "#";
            // 中间格子 → 输出 .
            else
                cout << ".";
        }
    return 0;
}

C - Fishbones

问题描述

艺术家高砂制作了一个鱼骨架形状的物体。

该物体由 NNN 根鱼刺1 根脊柱 组成。鱼刺编号为 111 到 NNN。 他想要在这 N+1N+1N+1 根骨头 上各写一个字符串,满足以下所有条件:

  • 写在脊柱 上的字符串长度恰好为 NNN。
  • 对于每一根鱼刺 i (i=1,...,N)i\ (i=1,\dots,N)i (i=1,...,N),满足:
    • 写在第 iii 根鱼刺上的字符串长度恰好为 AiA_iAi。
    • 第 iii 根鱼刺上字符串的第 BiB_iBi 个字符 ,与脊柱上字符串的第 iii 个字符相同。
  • 写在这 N+1N+1N+1 根骨头上的所有字符串,都必须是 S1,S2,...,SMS_1,S_2,\dots,S_MS1,S2,...,SM 中的某一个**(可以重复使用)**。

S1,...,SMS_1,\dots,S_MS1,...,SM 是由小写英文字母组成的字符串,且互不相同。

对于每个 j=1,...,Mj=1,\dots,Mj=1,...,M,请回答下面的问题:

  • 在所有满足条件的写法中,是否存在一种方案 ,使得脊柱上写的字符串恰好是 SjS_jSj?

解题思路

直接双重 for 循环判断,如果某一个字符串写在某一根肋骨上对应的字母是什么,接着用 map 维护第某一个字符能不能出现在脊柱的某一个位置。

最后枚举判断就可以了。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int maxn = 15, maxm = 2e5 + 5;
map<char, bool> M[15]; // M[i]:第i根鱼刺能匹配的字符集合
int n, m, a[maxn], b[maxn]; // n=鱼刺数,m=字符串数;a[i]=鱼刺i长度,b[i]=鱼刺i关键位置
string s[maxm]; // 存储所有候选字符串

int main()
{
    cin >> n;
    // 输入每根鱼刺的长度和关键位置
    for (int i = 1; i <= n; i++)
        cin >> a[i] >> b[i];
    
    cin >> m;
    // 输入所有候选字符串
    for (int i = 1; i <= m; i++)
        cin >> s[i];

    // 预处理:记录每根鱼刺能匹配的字符
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            // 长度符合要求,就记录该字符串的关键字符
            if (a[i] == s[j].size())
            {
                M[i][s[j][b[i] - 1]] = 1;
            }

    // 逐个判断每个字符串能否作为脊柱
    for (int i = 1; i <= m; i++)
    {
        int f = 1; // 标记是否合法
        // 长度必须等于n,否则直接不合法
        if (s[i].size() != n)
        {
            cout << "No\n";
            continue;
        }
        // 检查每一位字符是否都能被对应鱼刺匹配
        for (int j = 0; j < s[i].size(); j++)
            if (!M[j + 1][s[i][j]])
                f = 0;
        // 输出结果
        if (f)
            cout << "Yes\n";
        else
            cout << "No\n";
    }
    return 0;
}

D - No-Subsequence Substring

问题描述

给定由小写英文字母组成的字符串 SSS 和 TTT。

在 SSS 的非空子串 中,统计满足以下条件的子串数量: 不包含 TTT 作为其子序列(不需要连续)

注意:即使子串内容相同,只要取自不同位置,就视为不同的子串。

  • 子串 (substring) :从字符串 XXX 的开头删若干字符、结尾删若干字符(可以删0个)后得到的字符串。
  • 子序列 (subsequence) :从字符串 XXX 中删去若干字符(可以删0个),剩余字符保持原有顺序得到的字符串。

解题思路

我们可以用补集思想 简化计算:先求出 SSS 的非空子串总数 ,再减去包含 TTT 作为子序列的子串数量,最终结果即为答案。

我们从左到右遍历 SSS 的每个位置 iii,统计以 iii 为右端点且包含 TTT 的子串个数。维护数组 pospospos,其中 pos[j]pos[j]pos[j] 表示在遍历到当前字符时,恰好匹配 TTT 的前 jjj 个字符时,最靠右的合法左端点位置,这个记录能保证后续计算的最优性。

设 TTT 的长度为 len2len2len2,当遍历完成后,pos[len2]pos[len2]pos[len2] 存储了凑齐整个 TTT 时的最优左端点,此时以 iii 为右端点的合法子串数量就是 pos[len2]pos[len2]pos[len2]。

将所有右端点对应的数量累加得到总非法子串数,用总子串数减去该值,即可得到最终答案。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int maxt = 55;
string s, t;
int pos[maxt]; // pos[j]:匹配到T第j位时,最右的左端点位置

signed main()
{
    cin >> s >> t;
    int lens = s.size(), lent = t.size();
    
    // 总非空子串数量:n*(n+1)/2
    int ans = lens * (lens + 1) / 2;
    
    // 初始化:所有匹配状态初始为-1(未匹配)
    for (int i = 0; i < lent; i++)
        pos[i] = -1;

    // 遍历s的每个字符作为右端点
    for (int i = 0; i < lens; i++)
    {
        // 倒序更新,避免覆盖前面的pos值
        for (int j = lent - 1; j >= 1; j--)
            if (s[i] == t[j] && pos[j - 1] != -1)
                pos[j] = pos[j - 1];
        
        // 匹配T第一个字符,更新起点
        if (s[i] == t[0])
            pos[0] = i;
        
        // 已经能完整匹配T,减去非法子串数
        if (pos[lent - 1] != -1)
            ans -= pos[lent - 1] + 1;
    }

    // 输出答案:合法子串数量
    cout << ans;
    return 0;
}

E - You WILL Like Sigma Problem

题目翻译

你今日的幸运希腊字母是 σ(sigma)。来解决这道用到两次求和的题目,好运一定会降临于你。

给定长度为 NNN 的正整数序列 A=(A1,...,AN)A=(A_1,\dots,A_N)A=(A1,...,AN) 与长度为 MMM 的正整数序列 B=(B1,...,BM)B=(B_1,\dots,B_M)B=(B1,...,BM)。 求下面式子的值,结果对 998244353 取模:

∑i=1N∑j=1MAi∗Bj(imod  j) \sum_{i=1}^N \sum_{j=1}^{M} A_i * B_j (i \mod j) i=1∑Nj=1∑MAi∗Bj(imodj)

解题思路

首先我们给 AAA 数组补充一个元素 A0=0A_0 = 0A0=0。

对于固定的 jjj,它对答案的贡献可以写成: 0⋅A0Bj+1⋅A1Bj+⋯+(j−1)⋅Aj−1Bj+0⋅AjBj+...0 \cdot A_0 B_j + 1 \cdot A_1 B_j + \dots + (j-1) \cdot A_{j-1} B_j + 0 \cdot A_j B_j + \dots0⋅A0Bj+1⋅A1Bj+⋯+(j−1)⋅Aj−1Bj+0⋅AjBj+...

这一形式非常适合用分块思想 处理:以 0,1,...,j−10,1,\dots,j-10,1,...,j−1 为一个周期进行分块,同时维护两个前缀和数组:

  1. i⋅Aii \cdot A_ii⋅Ai 的前缀和
  2. AiA_iAi 的前缀和

假设当前处于第 kkk 个块(从 000 开始编号),直接用公式 ∑i⋅Ai−j⋅k∑Ai\sum i\cdot A_i - j \cdot k \sum A_i∑i⋅Ai−j⋅k∑Ai 就能算出当前块的贡献,最后整体乘上 BjB_jBj 计入答案即可。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;

const int maxn = 5e5 + 5, MOD = 998244353;
int a[maxn], b[maxn];
int sum1[maxn]; // sum1[i] = 前缀和 i*a[i]
int sum2[maxn]; // sum2[i] = 前缀和 a[i]
int n, m;

signed main()
{
    cin >> n >> m;
    // 输入数组 A
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    // 输入数组 B
    for (int i = 1; i <= m; i++)
        cin >> b[i];

    // 预处理两个前缀和数组
    for (int i = 1; i <= 500000; i++)
    {
        sum1[i] = (sum1[i-1] + i * a[i] % MOD) % MOD;
        sum2[i] = (sum2[i-1] + a[i] % MOD) % MOD;
    }

    int ans = 0;
    // 枚举 j,用整除分块快速计算 i mod j
    for (int j = 1; j <= m; j++)
    {
        // 按 j 的倍数分块计算每一段 [k*j+1, (k+1)*j]
        for (int k = 0; k * j < n; k++)
        {
            int l = k * j;
            int r = min((k+1)*j - 1, n);
            // 利用公式 i mod j = i - j * floor(i/j)
            int res = 0;
            // 累加 i*a[i]
            res = (res + sum1[r] - sum1[l] + MOD) % MOD;
            // 减去 j*k * a[i] 之和
            res = (res - (sum2[r] - sum2[l] + MOD) % MOD * (j * k % MOD) % MOD + MOD) % MOD;
            // 乘上 b[j] 计入答案
            ans = (ans + res * b[j] % MOD) % MOD;
        }
    }

    cout << ans;
    return 0;
}

F - Interval Inversion Count

题目翻译

给定正整数 NNN 和一个由 1,2,...,N1,2,\dots,N1,2,...,N 组成的排列 P=(P1,P2,...,PNP=(P_1,P_2,\dots,P_NP=(P1,P2,...,PN。 给定整数 KKK,求满足以下两个条件的整数对 (l,r)(l,r)(l,r) 的个数:

  • 1≤l≤r≤N1\le l\le r\le N1≤l≤r≤N
  • 子序列 Pl,Pl+1,...,PrP_l,P_{l+1},\dots,P_rPl,Pl+1,...,Pr 的逆序对的数量恰好等于 KKK

解题思路

我们可以观察到一个非常直观的性质:当使用滑动窗口表示子区间时,窗口向右扩大,区间内的逆序对数量只会单调不减

基于这个性质,我们可以使用双指针(滑动窗口) 来维护合法区间,同时用树状数组动态维护当前窗口内的逆序对数量。

具体做法是:枚举左端点,然后不断将右端点向右扩展,直到区间内的逆序对数量即将超过 K。此时以当前左端点开头的所有合法区间都可以直接统计。

代码

cpp 复制代码
#include <bits/stdc++.h>

// 太水了,我直接抄板子了
using namespace std;

// 树状数组:维护区间和,用于快速统计逆序相关数量
struct FenwickTree
{
    vector<long long> tree;
    int n;
    FenwickTree(int size) : n(size), tree(size + 1, 0) {}

    // 单点修改
    void add(int idx, int val)
    {
        idx++;
        while (idx <= n)
        {
            tree[idx] += val;
            idx += idx & -idx;
        }
    }

    // 查询前缀和
    long long query(int idx)
    {
        idx++;
        long long res = 0;
        while (idx > 0)
        {
            res += tree[idx];
            idx -= idx & -idx;
        }
        return res;
    }

    // 查询 [l, r) 的和
    long long sum(int l, int r)
    {
        if (l >= r) return 0;
        return query(r - 1) - query(l - 1);
    }
};

// 计算逆序数 ≤ X 的区间 [l,r] 数量
long long count_less_equal(int N, vector<int> &P, long long X)
{
    FenwickTree bit(N);
    long long now_inv = 0; // 当前窗口逆序数
    long long ans = 0;
    int r = 0;

    // 双指针 + 树状数组 维护合法右端点
    for (int l = 0; l < N; ++l)
    {
        if (r < l) r = l;
        // 扩展 r,直到逆序数超 X
        while (r < N)
        {
            long long add = bit.sum(P[r], N); // 新增 r 带来的逆序增量
            if (now_inv + add > X) break;
            now_inv += add;
            bit.add(P[r], 1);
            r++;
        }
        ans += r - l; // 以 l 为左端点的合法区间数

        // 移除左端点 l
        now_inv -= bit.sum(0, P[l]);
        bit.add(P[l], -1);
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N;
    long long K;
    cin >> N >> K;

    vector<int> P(N);
    for (int i = 0; i < N; ++i)
    {
        cin >> P[i];
        P[i]--; // 转为 0 下标
    }

    // 答案 = 逆序 ≤ K 的区间数 - 逆序 ≤ K-1 的区间数
    long long res = count_less_equal(N, P, K);
    if (K > 0)
        res -= count_less_equal(N, P, K - 1);

    cout << res << endl;
    return 0;
}

G - 221 Substring

题目翻译

对于正整数序列 X=(X1,...,Xn)X=(X_1,\dots,X_n)X=(X1,...,Xn),如果其游程编码 中每一段的段长段内数值 都相等,我们就称 X 为 221 序列

形式化地,满足以下条件的序列称为 221 序列:

  • 对任意满足 1≤l≤r≤n1\le l\le r\le n1≤l≤r≤n 的整数对 (l,r)(l,r)(l,r),若以下三个条件同时成立,则必有 r−l+1=Xlr-l+1 = X_lr−l+1=Xl:
    *
    1. l=1l=1l=1,或者 l≥2l\ge2l≥2 且 Xl−1≠XlX_{l-1}\neq X_lXl−1=Xl(段的开头)
      1. r=nr=nr=n,或者 r≤n−1r\le n-1r≤n−1 且 Xr+1≠XrX_{r+1}\neq X_rXr+1=Xr(段的结尾)
      1. Xl=Xl+1=⋯=XrX_l=X_{l+1}=\dots=X_rXl=Xl+1=⋯=Xr(整段数值相同)

换句话说:每一段相同数字构成的连续段,其长度必须恰好等于段内的数字

例如:

  • (2,2,3,3,3,1,2,2)(2,2,3,3,3,1,2,2)(2,2,3,3,3,1,2,2) 是 221 序列
  • (1,1)(1,1)(1,1) 和 (4,4,1,4,4)(4,4,1,4,4)(4,4,1,4,4) 不是 221 序列

给定长度为 NNN 的正整数序列 A=(A1,...,AN)A=(A_1,\dots,A_N)A=(A1,...,AN),求 AAA 中本质不同 的非空连续子串里,是 221 序列的个数。

解题思路

这道题可借助后缀数组求解,核心思路是先对原序列 A 进行预处理,将问题转化为求解特定子串的数量,具体优化润色如下:

首先观察原序列 A 的特性:对于 A 中连续的 k 个相同数字 x,根据 k 与 x 的大小关系,可分为三种情况处理,这是后续预处理的关键:

  1. 若 k<xk<xk<x:这 k 个 x 无法构成合法的 221 序列段,也不能与前后的数字拼接形成合法段,相当于 "无效段";
  2. 若 k=xk=xk=x:这 k 个 x 本身是合法的 221 序列段,且可灵活与前后的合法段拼接(可连前、连后、都连或都不连);
  3. 若 k>xk>xk>x:这 k 个 x 可拆分为合法段,但只能与前后合法段中的一侧拼接(无法同时与两侧拼接,否则会破坏段长与数值相等的规则)。

基于以上分析,我们对原序列 A 进行预处理,构造一个新序列 S(初始为空),处理规则如下:

  1. 遇到上述第 1 种情况(k<xk<xk<x):向 S 中加入一个标记 0(用于分隔无效段,避免无效拼接);
  2. 遇到上述第 2 种情况(k=xk=xk=x):直接向 S 中加入 x(保留合法段,允许灵活拼接);
  3. 遇到上述第 3 种情况(k>xk>xk>x):向 S 中依次加入 x、0、x(用 0 分隔两个可独立拼接的合法段,限制只能单侧拼接)。

举个例子,若原序列 A=(2,2,3,3,3,1,1,1,3,3,3,1,2,2,2,1,9,1,4,4,4,4,4)A=(2,2,3,3,3,1,1,1,3,3,3,1,2,2,2,1,9,1,4,4,4,4,4)A=(2,2,3,3,3,1,1,1,3,3,3,1,2,2,2,1,9,1,4,4,4,4,4),经过预处理后得到的新序列 S=(0,2,3,1,0,1,3,1,2,0,2,1,0,1,4)S=(0,2,3,1,0,1,3,1,2,0,2,1,0,1,4)S=(0,2,3,1,0,1,3,1,2,0,2,1,0,1,4)。

此时,原问题就被转化为一个更简单的问题:求解序列 S 中不包含 0、且本质不同的非空子串数量。后续只需使用后缀数组模板,对处理后的序列 S 进行求解即可(后缀数组可高效统计本质不同的子串个数)。

代码

cpp 复制代码
#include <bits/stdc++.h>
#define int long long

using namespace std;

int n; // 原序列A的长度

// 核心预处理函数:将原序列A转化为新序列S
// 处理逻辑:按连续相同数字的段长k与数字x的关系,构造S(0用于分隔无效/不可拼接段)
vector<int> build_s(vector<int> &a)
{
    vector<int> s; // 存储预处理后的新序列S
    int p = 0, t = -1; // p:当前连续段的数字;t:当前连续段的长度(计数)
    for (int i : a) // 遍历原序列A的每个元素
    {
        if (i != p) // 遇到新的数字,说明上一个连续段结束,处理上一段
        {
            // 上一段的长度t ≥ 数字p(说明上一段可构成合法221段)
            if (t >= p)
            {
                s.push_back(p); // 加入合法段p
                if (t > p) // 若段长t > p,需用0分隔,只能单侧拼接
                {
                    s.push_back(0);
                    s.push_back(p);
                }
            }
            else // 上一段长度t < p,无法构成合法段,加入0标记无效
                s.push_back(0);
            p = i, t = 1; // 更新当前段的数字为i,长度初始化为1
        }
        else // 与当前段数字相同,段长+1
            t++;
    }
    // 处理最后一个连续段(循环结束后未处理)
    if (t >= p)
        s.push_back(p);
    return s; // 返回预处理后的序列S
}

// 后缀数组构建函数(模板):将序列s转化为后缀数组sa
// 后缀数组sa[i]:表示s中排名第i的后缀的起始下标
vector<int> build_sa(vector<int> &s)
{
    int n = s.size(), m = 10; // m:初始字符集大小(s中元素为0~9,故初始m=10)
    vector<int> sa(n), rk(n), cnt(max(n, m)), old_rk(n, 0), px(n), id(n);
    
    // 初始化:统计每个字符的出现次数,确定初始排名
    for (int i = 0; i < n; i++)
        cnt[rk[i] = s[i]]++; // rk[i]:初始排名(直接用s[i]作为排名,因为s[i]范围小)
    for (int i = 1; i < m; i++)
        cnt[i] += cnt[i - 1]; // 前缀和,用于确定每个排名对应的后缀位置
    for (int i = 0; i < n; i++)
        sa[--cnt[rk[i]]] = i; // 构建初始后缀数组
    
    // 倍增法优化后缀数组构建(核心步骤)
    for (int k = 1; k <= n; k <<= 1) // k:当前比较的长度(每次翻倍)
    {
        int p = 0;
        // 先处理长度不足k的后缀(排名最前,直接放入id数组)
        for (int i = n - k; i < n; i++)
            id[p++] = i;
        // 处理长度足够的后缀,按上一轮排名排序,放入id数组
        for (int i = 0; i < n; i++)
            if (sa[i] >= k)
                id[p++] = sa[i] - k;
        
        // 重新计算排名rk
        for (int &i : cnt)
            i = 0; // 重置计数数组
        for (int i = 0; i < n; i++)
            cnt[px[i] = rk[id[i]]]++; // px[i]:id[i]对应的上一轮排名
        for (int i = 1; i < m; i++)
            cnt[i] += cnt[i - 1];
        for (int i = n - 1; i >= 0; i--)
            sa[--cnt[px[i]]] = id[i]; // 重新构建sa
        
        // 更新rk数组,区分不同后缀的排名
        swap(rk, old_rk); // old_rk保存上一轮的排名
        p = 1; // 新排名的起始值(从1开始)
        for (int i = 1; i < n; i++)
        {
            // 若两个后缀的前k个字符和后k个字符排名都相同,说明排名相同
            if (old_rk[sa[i]] == old_rk[sa[i - 1]] && old_rk[sa[i] + k] == old_rk[sa[i - 1] + k])
                rk[sa[i]] = p - 1;
            else // 否则排名+1
                rk[sa[i]] = p++;
        }
        if (p == n)
            break; // 若所有后缀排名都不同,提前结束(优化)
        m = p; // 更新字符集大小为当前排名数
    }
    return sa;
}

// 高度数组构建函数(模板):计算height数组,用于统计本质不同的子串
// height[i]:排名第i的后缀与排名第i-1的后缀的最长公共前缀(LCP)
vector<int> build_height(vector<int> sa, vector<int> s)
{
    int n = s.size();
    vector<int> height(n), rk(n);
    // 先构建rk数组(sa的逆数组:rk[sa[i]] = i,即后缀起始下标为sa[i]的排名是i)
    for (int i = 0; i < n; i++)
        rk[sa[i]] = i;
    int k = 0; // 用于记录当前公共前缀的长度
    for (int i = 0; i < n; i++)
    {
        if (rk[i] == 0)
            continue; // 排名第0的后缀没有前一个后缀,height[0]默认为0
        if (k)
            k--; // 优化:公共前缀长度最多比上一个少1
        int j = sa[rk[i] - 1]; // 找到排名前一位的后缀起始下标j
        // 计算i和j对应的后缀的最长公共前缀长度
        while (i + k < n &amp;&amp; j + k < n && s[i + k] == s[j + k])
            k++;
        height[rk[i]] = k; // 记录当前排名的height值
    }
    return height;
}

signed main()
{
    ios::sync_with_stdio(false); // 关闭同步,加快输入速度
    cin.tie(nullptr); // 解除cin与cout的绑定,进一步提速
    
    cin >> n;
    vector<int> a; // 存储原序列A
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        a.push_back(x);
    }
    
    // 步骤1:预处理原序列A,得到新序列S(0分隔无效/不可拼接段)
    vector<int> s = build_s(a);
    // 步骤2:构建S的后缀数组sa
    vector<int> sa = build_sa(s);
    // 步骤3:构建height数组,用于统计本质不同的子串
    vector<int> height = build_height(sa, s);
    
    int m = s.size(); // 新序列S的长度
    vector<int> len(m + 1); // len[i]:以S[i]为起点,不包含0的最长连续子串长度(合法子串的最大长度)
    len[m] = 0; // 边界:S[m]超出范围,长度为0
    // 倒序计算len数组(从后往前,避免重复计算)
    for (int i = m - 1; i >= 0; i--)
        if (s[i] == 0) // 遇到0,当前起点无法构成合法子串,长度为0
            len[i] = 0;
        else // 非0,长度 = 下一个位置的长度 + 1
            len[i] = len[i + 1] + 1;
    
    // 步骤4:统计答案:本质不同、不包含0的非空子串数量
    int sum = 0; // 存储最终答案
    for (int i = 0; i < m; i++)
    {
        // l:当前后缀与前一个后缀的最长公共前缀长度(重复子串的长度)
        int l = (i == 0) ? 0 : height[i];
        // r:当前后缀的最大合法长度(以sa[i]为起点,不包含0的最长子串长度)
        int r = len[sa[i]];
        // 新增的本质不同子串数量 = 最大合法长度 - 重复长度(若为负则取0)
        sum += max(0ll, r - l);
    }
    
    cout << sum; // 输出答案
    return 0;
}
相关推荐
feifeigo1234 小时前
基于马尔可夫随机场模型的SAR图像变化检测源码实现
算法
fengfuyao9854 小时前
基于STM32的4轴步进电机加减速控制工程源码(梯形加减速算法)
网络·stm32·算法
无敌昊哥战神5 小时前
深入理解 C 语言:巧妙利用“0地址”手写 offsetof 宏与内存对齐机制
c语言·数据结构·算法
小白菜又菜5 小时前
Leetcode 2075. Decode the Slanted Ciphertext
算法·leetcode·职场和发展
Proxy_ZZ06 小时前
用Matlab绘制BER曲线对比SPA与Min-Sum性能
人工智能·算法·机器学习
黎阳之光6 小时前
黎阳之光:以视频孪生领跑全球,赋能数字孪生水利智能监测新征程
大数据·人工智能·算法·安全·数字孪生
小李子呢02116 小时前
前端八股6---v-model双向绑定
前端·javascript·算法
XH华6 小时前
数据结构第九章:树的学习(下)
数据结构·学习
2301_822703207 小时前
Flutter 框架跨平台鸿蒙开发 - 创意声音合成器应用
算法·flutter·华为·harmonyos·鸿蒙