跳转至

Welcome to Liam's Blog

About me

  • My id : lnw143

  • Year of Birth : 2009

  • Location : Zhongshan, Guangdong Province, CHN

Motto

Nothing.

后缀数组

后缀数组是处理字符串的利器。

定义

对于长度为 \(n\) 的字符串 \(s = s_1 s_2 \cdots s_n\),定义 \(s[i,j] = s_i s_{i+1} \cdots s_j\),即 \(s\) 的第 \(i\) 个到第 \(j\) 个字符形成的子串。

定义 sa\(s\) 的后缀数组,sa[i] 表示第 \(i\) 小的后缀的编号。

定义 rk[sa[i]]=i,即 rk[i] 表示后缀 \(i\) 的 rank。

求解

考虑最暴力的做法,取出所有后缀并排序,空间复杂度 \(O(n ^ 2)\),时间复杂度 \(O(n^2 \log n)\)

考虑倍增,设 rk[j][i] 表示在所有长度为 \(2^j\) 的后缀中,后缀 \(s[i,i+2^j-1]\) 的 rank(超出 \(n\) 的部分记为空字符,小于任何非空字符)。

显然我们可以 naively 得到 rk[0][i],考虑如何由 rk[j-1] 得到 rk[j]

发现可以将所有二元组 (rk[j-1][i],rk[j-1][i+(1<<(j-1))] 排序,即把每段后缀分为前后两段,并以前一半的 rank 作为第一关键字,后一半的 rank 作为第二关键字,超出 \(n\) 的部分记为 0,排序后得到 rk[j]

总共倍增 \(\mathrm O(\log n)\) 次,每次排序 \(\mathrm O(n \log n)\),总时间复杂度 \(\mathrm O(n \log^2 n)\)

发现 rk 的值域是 \(\mathrm O(n)\) 的,因此可以用桶排做到 \(O(n \log n)\)

在算法竞赛中这已经足够了,如果追求更优的时间复杂度,可以考虑 SA-IS / DC3 算法,线性时间复杂度,但实现十分复杂,在竞赛中不常用,因此不再赘述。

显然通过 rk 可以 \(\mathrm O(n)\) 得到 sa

height 数组

定义 ht[i] 为后缀 sa[i]sa[i-1] 的最长公共前缀的长度,并设 ht[1]=0

一眼看来似乎没有线性做法,但是我们将尝试证明以下引理:

Lemma: ht[rk[i]] \(\ge\) ht[rk[i-1]]-1


Proof:

ht[rk[i-1]] \(\le 1\) 的情况是 naive 的。

ht[rk[i-1]] \(\ge 2\),即 sa[rk[i-1]-1]i-1 的最长公共前缀长度 \(\ge 2\),设这个最长公共前缀为 \(aA\),其中 \(a\) 是单个字符,\(A\) 是一个非空字符串。

那么设后缀 sa[rk[i-1]-1]\(aAB\)i-1\(aAC\),则 \(B \lt C\),且后缀 sa[rk[i-1]-1]+1\(AB\)i\(AC\)

后缀 sa[rk[i]-1] 因为只比 i 前一名,所以是小于 i 中最大的,又因为 \(AB \lt AC\),所以 \(AB \le\) 后缀 sa[rk[i]-1] \(\lt AC\),容易看出后缀 i 和后缀 sa[rk[i]-1] 有公共前缀 \(A\)

于是 ht[rk[i]] $\ge |A| = $ ht[rk[i-1]]-1

\(\square\)


由此,我们从 ht[rk[i-1]]-1 开始暴力拓展 ht[rk[i]],在 \(\mathrm O(n)\) 的时间复杂度内得到 ht

ht 数组有这样一个性质:后缀 i,j 的最长公共前缀长度等于 ht[rk[i],rk[j]) 中的最小值。(钦定 rk[i] \(\lt\) rk[j]

因此我们可以用 ST 表 \(\mathrm O(n \log n)\) 预处理后 \(\mathrm O(1)\) 询问任意后缀的最长公共前缀长度。

例题

UOJ 35.后缀排序
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5;
int n,sa[N + 2],cnt[N + 2],rk[2][N + 2],ht[N + 2];
char s[N + 2];
struct node {
    int x,y,id;
} p[N + 2],q[N + 2];
bool operator<(const node& a,const node& b) {
    return a.x!=b.x?a.x<b.x:a.y<b.y;
}
bool operator==(const node& a,const node& b) {
    return a.x==b.x&&a.y==b.y;
}
void Sort() {
    memset(cnt,0,sizeof(*cnt)*(n+1));
    for(int i=1; i<=n; ++i) ++cnt[p[i].y];
    for(int i=1; i<=n; ++i) cnt[i]+=cnt[i-1];
    for(int i=n; i>=1; --i) q[cnt[p[i].y]--]=p[i];
    memset(cnt,0,sizeof(*cnt)*(n+1));
    for(int i=1; i<=n; ++i) ++cnt[q[i].x];
    for(int i=1; i<=n; ++i) cnt[i]+=cnt[i-1];
    for(int i=n; i>=1; --i) p[cnt[q[i].x]--]=q[i];
}
int main() {
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=1; i<=n; ++i) cnt[s[i]]=1;
    for(int i=1; i<128; ++i) cnt[i]+=cnt[i-1];
    for(int i=1; i<=n; ++i) rk[0][i]=cnt[s[i]];
    for(int j=1,k=1; j<=n; j<<=1,k^=1) {
        for(int i=1; i<=n; ++i) p[i]={rk[k^1][i],i+j<=n?rk[k^1][i+j]:0,i};
        Sort();
        for(int i=1,t=0; i<=n; ++i)
            if(i>1&&p[i]==p[i-1])
                rk[k][p[i].id]=t;
            else
                rk[k][p[i].id]=++t;
        if((j<<1)>n) swap(rk[k],rk[0]);
    }
    for(int i=1; i<=n; ++i) sa[rk[0][i]]=i;
    for(int i=1; i<=n; ++i) printf("%d ",sa[i]);
    putchar('\n');
    for(int i=1; i<=n; ++i) {
        if(rk[0][i]==1) continue;
        int k=i>1?max(0,ht[rk[0][i-1]]-1):0;
        while(i+k<=n&&sa[rk[0][i]-1]+k<=n&&s[i+k]==s[sa[rk[0][i]-1]+k]) ++k;
        ht[rk[0][i]]=k;
    }
    for(int i=2; i<=n; ++i) printf("%d ",ht[i]);
    return 0;
}

杂项

这里是一些杂七杂八的东西。

VSCode 配置

前言

作为 VSCode 资深用户,从第一次下载到如今,踩了一个又一个坑,在这里记录一下我的配置方法以及遇到的一些问题。

准备

在官网下载后,点开安装程序,建议勾选以下所有选项:

VSCode 安装程序

等待安装完毕即可。

插件

插件是 VSCode 的核心,其强大的插件开发社区是一大亮点。

打开左侧的扩展管理器,搜索并点击安装便可将插件添加到 VSCode。

如搜索 chinese,安装中文语言包。

可以在终端用 code --install-extension <extension-id> 或按 F1 / Ctrl+Shift+P 打开命令面板,搜索 ext install 并选择 Extensions: Install Extension,输入插件名称或 ID 安装插件。

附录 有一些常用的插件。

配置

VSCode 默认是不支持编译运行 C++ 的,需要安装插件 C/C++ 并配置文件。

Ctrl+K Ctrl+O 打开一个工作文件夹,并新建 .vscode 文件夹,并新建两个文件 tasks.jsonlaunch.json

Tasks

tasks.json 文件用来配置编译任务,可以配置多个编译任务,每个任务对应一个编译命令。

尝试在 tasks.json 文件中添加以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C++14",
            "command": "g++",
            "args": [
                "${file}",
                "-o",
                "${fileDirname}\\a.exe",
                "-std=c++14",
                "-Wall",
                "-Wextra",
                "-g",
                "-Wl,--stack=128000000",
                "-fno-ms-extensions"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "type": "cppbuild",
            "label": "C++14 With O2",
            "command": "g++",
            "args": [
                "${file}",
                "-o",
                "${fileDirname}\\a.exe",
                "-std=c++14",
                "-O2",
                "-Wall",
                "-Wextra",
                "-g",
                "-Wl,--stack=128000000",
                "-fno-ms-extensions"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": false
            }
        },
    ],
    "version": "2.0.0"
}

其中 label 字段是任务的名称,command 字段是编译器,args 字段是编译命令,cwd 字段是编译命令的运行目录,isDefault 字段是是否为默认编译任务。

之所以输出为 a.exe,是为了兼容源代码文件名中的中文字符,如果不需要,可以改为 ${fileBasenameNoExtension}.exe

Warning

非必要不要更改 cwdtypeproblemMatcher等字段,否则可能导致任务执行失败。

Launch

launch.json 文件用来配置调试任务,可以配置多个调试任务,每个任务对应一个调试命令。

尝试在 launch.json 文件中添加以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Launch Program",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}\\a.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${fileDirname}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C++14"
        }
    ]
}

其中一般只需要配置 preLaunchTaskprogram 字段,前者指定编译任务,后者指定运行程序。

全局配置

你是否有这样的烦恼:每次新建 VSCode 工作文件夹都要配置 tasks.jsonlaunch.json?每次编译都会自动生成 .vscode 文件夹?

VSCode 的全局 tasks.json 配置位于 %APPDATA%\Code\User\tasks.json,可以编辑该文件来配置全局编译任务。

launch.json 配置位于 %APPDATA%\Code\User\settings.json,在其中加入以下内容:

1
"launch": < launch.json 配置内容 >

这样即可实现一次配置,终身受益(

附录

常用插件

Tools
Themes

8113. 【2022.10.7联考noip模拟】Talulah

题意

给定长度为 \(n\) 的排列 \(p\),定义集合 \(S_i = \{j \mid j \ge i \land \max_{k \in [i,j]} p_k = p_j\}\)

给定 \(q\) 次询问 \(l,r\),求 \(\sum_{x,y \in [l,r]} |S_x \cap S_y|\)

\(n,q \le 2.5 \times 10^5\)

思考

考虑钦定 \(x<y\),如下图所示:

图解

发现 \(\forall i \ge y\)\(i \in S_x \Rightarrow i \in S_y\)

Proof: 因为 \(i \in S_x\),所以 \(\max_{k \in [x,i]} p_k = p_i\),因此显然 \(\max_{k \in [y,i]} p_k = p_i\),即 \(i \in S_y\)\(\square\)

因此 \(|S_x \cap S_y| = |S_x \cap [x,y)| + |S_y|\)

考虑把这两个部分分开处理,发现所有 \(|S_y|\) 即为 \(\sum_{i \in [l,r]} (i-l)|S_i|\),可以用前缀和 \(\mathrm O(1)\) 计算。

考虑计算 \(\sum_{l \le x \lt y \le r} |S_x \cap [x,y)|\),如下图所示:

图解

正难则反,考虑抛弃 \(y\) 的限制,而是计算 \(i \in S_x\) 的贡献。

发现 \(\forall i \in S_x \cap [x,r]\),对于 \(\forall y \gt i\) 都有 \(1\) 的贡献,即原式等于 \(\sum_{i \in S_x \cap [x,r]} r-i\)

发现这个不好维护,于是正难则反,考虑计算 \(\forall i \in [l,r]\)\(x \le i\) 的贡献。

\(\mathrm{pre}_i = \max_{j \lt i} {j \mid p_j \gt p_i}\),即 \(i\) 前第一个大于 \(i\) 的元素的位置,并记 \(p_0 = \infty\)

发现 \(\forall i \in [l,r]\)\(i\)\(x \in (\mathrm{pre}_i,i]\)\(r-i\) 的贡献。

由于我们不清楚 \(r\) 的值,因此我们维护 \(r\) 的系数即可。

图解

如上图,考虑用线段树维护区间加与区间和。

考虑 \(i \in [l,r]\) 这条限制,容易发现当 \(i \lt l\) 时,\(i\)\([l,r]\) 无贡献;当 \(i \gt r\) 时,如上图的 \(j\),会对 \([l,r]\) 产生多余的贡献。

因此我们把询问离线并按右端点排序,依次加点即可。

最后别忘了我们计算的是 \(x \lt y\) 的答案,剩下部分是 naive 的。

如果强制在线的话,可以用可持久化线段树 + 标记永久化解决,时间复杂度同为 \(\mathrm O((n+q) \log n)\)

图论

这里是图论相关的算法笔记。

行列式

定义

定义 \(n\) 阶方阵 \(A\) 的行列式为:

\[ \det A=\begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} =\sum_{p_1,p_2,\cdots,p_n} (-1)^{\tau(p_1,p_2,\cdots,p_n)} \prod_{i=1}^n a_{i,p_i} \]

其中 \(\{p_1,p_2,\cdots,p_n\}\) 是一个 \(n\) 的排列,\(\tau(p_1,p_2,\cdots,p_n)\) 表示 \(\{p_1,p_2,\cdots,p_n\}\) 的逆序数。

引理

Lemma 1

\[ \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ ka_{i,1} & ka_{i,2} & \cdots & ka_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} = k \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} \]

读者自证不难。

Lemma 2

\[ \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{j,1} & a_{j,2} & \cdots & a_{j,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} = -\begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{j,1} & a_{j,2} & \cdots & a_{j,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} \]

读者自证不难。

Lemma 3

若矩阵有相同的两行,则行列式为零。

Proof: 交换相同的两行后矩阵不变,且由 Lemma 2 得行列式变号,即 \(|A|=-|A|\),所以 \(|A|=0\)\(\square\)

Lemma 4

若矩阵某一行是另一行的倍数,则行列式为零。

Proof:Lemma 1 & Lemma 3 易证。

Lemma 5

\(\forall j,a_{i,j} = b_j + c_j\),则

\[ \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} = \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ b_1 & b_2 & \cdots & b_n \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} + \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ c_1 & c_2 & \cdots & c_n \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} \]

读者自证不难。

Lemma 6

\[ \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{j,1} & a_{j,2} & \cdots & a_{j,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} = \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{j,1}+ka_{i,1} & a_{j,2}+ka_{i,2} & \cdots & a_{j,n}+ka_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} \]

Proof:

\[ A = \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{j,1} & a_{j,2} & \cdots & a_{j,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix}, B = \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ ka_{i,1} & ka_{i,2} & \cdots & ka_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} \\ C = \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ a_{2,1} & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & & \vdots \\ a_{i,1} & a_{i,2} & \cdots & a_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{j,1}+ka_{i,1} & a_{j,2}+ka_{i,2} & \cdots & a_{j,n}+ka_{i,n} \\ \vdots & \vdots & & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \\ \end{vmatrix} \]

Lemma 4\(|B|=0\),由 Lemma 5\(|C| = |A| + |B| = |A|\)\(\square\)

Lemma 7

\[ \begin{vmatrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n} \\ 0 & a_{2,2} & \cdots & a_{2,n} \\ \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & \cdots & a_{n,n} \\ \end{vmatrix} = \prod_i a_{i,i} \]

即上三角矩阵的行列式为主对角线元素的乘积。

Proof: 由行列式的定义得若 \(\exists i, a_{i,p_i} = 0\),那么整个排列对行列式无贡献。

因此要想对行列式有贡献,\(p_n\) 必等于 \(n\),同理得 \(\forall i, p_i = i\)\(\square\)

求解

显然有 \(\mathrm O(n! n)\) 的 naive 方法,但如果这样上面的 \(\LaTeX\) 就白写了(

考虑把矩阵转化为上三角矩阵并由 Lemma 7 \(\mathrm O(n)\) 计算行列式。

这个过程形如高斯消元,于是考虑用高斯消元法来求解行列式。

假设目前处理到第 \(i\) 行,要使得 \(\forall j > i, a_{j,i} = 0\)

显然可以让第 \(j\) 行加上第 \(i\) 行乘 \(- \frac{a_{i,j}}{a_{i,i}}\),但对于任意模数情况下 \(a_{i,i}\) 不一定有逆元。

因此,我们考虑用辗转相除法。

每次使得 \(a_{j,i} \gets a_{j,i} \bmod a_{i,i}\),并交换第 \(i\) 行与第 \(j\) 行,直到 \(a_{i,i}\)\(0\)

即让第 \(j\) 行加上第 \(i\) 行乘 \(- \lfloor \frac{a_{i,j}}{a_{i,i}} \rfloor\)

时间复杂度看似是 \(\mathrm O(n^3 \log P)\),但可以证明是 \(\mathrm O(n^3)\),其中 \(P\) 是模数。

参考代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
int n,p,a[N + 2][N + 2];
int det() {
    int sgn=0;
    for(int i=1; i<=n; ++i)
        for(int j=i+1; j<=n; ++j) {
            while(a[i][i]) {
                ll d=a[j][i]/a[i][i];
                for(int k=1; k<=n; ++k)
                    a[j][k]=(a[j][k]-d*a[i][k]%p)%p;
                swap(a[i],a[j]);
                sgn^=1;
            }
            swap(a[i],a[j]);
            sgn^=1;
        }
    ll ans=sgn?-1:1;
    for(int i=1; i<=n; ++i) ans=ans*a[i][i]%p;
    return (ans+p)%p;
}

应用

  • 矩阵树定理

8081.【2024年SD省队集训 day2】反转了 (flip)

题意

给定长度为 \(N\) 的数组 \(A\),定义 \(S_0 = 0\)\(S_i = \sum_{j=1}^i A_j(i \in [1,N])\)

\(K \in [0,N]\),求解:

  • 至多反转 \(K\) 个元素的符号,求 \(\max_{i \in [0,N]} {S_i}\) 的最大值。

朴素解法

暴力枚举 \(K\),并枚举 \(i \in [0,N]\),假定 \(S_i = \max_{j \in [0,N]} {S_j}\),显然最优方案为反转前 \(i\) 个元素中绝对值最大的 \(K\) 个元素。

\(B_i = \max(0,-A_i)\),那么第 \(i\) 段前缀对 \(K\) 的贡献为 \(C(i,K) = S_i + 2 W(i,K)\),其中 \(W(i,K)\)\(\{B_1,B_2,\dots,B_i\}\) 中的前 \(K\) 大元素的和。

答案即为 \(\max{C(i,K)}\)

注意当 \(i \lt K\) 时,\(W(i,K) = \sum_{j=1}^i B_j\)

\(W(i,K)\) 可以用主席树在 \(O(\log N)\) 时间内求得。

总时间复杂度 \(O(N ^ 2 \log N)\),期望得分 30。

优化

\(f_K\) 表示 \(\min \{i | \forall j,C(i,K) \ge C(j,K)\}\),即使得 \(C(i,K)\) 最大的最小的 \(i\) 值。

容易发现 \(f\) 非严格单调递增。

于是便如 ABC348G 一样,用分治 + 主席树解决。

单调性证明

实践是检验真理的唯一标准
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int n,a[N + 2];
long long s[N + 2];
int main() {
    scanf("%d",&n);
    for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
    for(int i=1; i<=n; ++i) s[i]=s[i-1]+a[i];
    int lst=-1;
    for(int i=0; i<=n; ++i) {
        multiset<int> st;
        ll sum=0,ans=0,k=0;
        for(int j=1; j<=n; ++j) {
            if(i!=0&&a[j]<0&&(st.size()<i||*st.begin()<-a[j])) {
                if(st.size()==i) {
                    sum-=*st.begin();
                    st.erase(st.begin());
                }
                sum+=-a[j];
                st.insert(-a[j]);
            }
            if(s[j]+sum*2>ans) ans=s[j]+sum*2,k=j;
        }
        assert(lst<=k);
        lst=k;
        printf("%lld ",ans);
    }
    return 0;
}

考虑使用反证法,假设 \(\exists i,f_i > f_{i+1}\),设 \(f_i = x,f_{i+1} = y\)

因为 \(f_i = x\),于是有

\[ \begin{aligned} & C(y,i) \lt C(x,i) \\ & S_y + 2W(y,i) \lt S_x + 2W(x,i) \\ & S_y - S_x + 2W(y,i) - 2W(x,i) \lt 0 \\ \end{aligned} \]

因为 \(f_{i+1} = y\),于是有

\[ \begin{aligned} C(y,i+1) & \ge C(x,i+1) \\ S_y + 2W(y,i+1) & \ge S_x + 2W(x,i+1) \\ S_y - S_x + 2W(y,i+1) - 2W(x,i+1) & \ge 0 \\ & \gt S_y - S_x + 2W(y,i) - 2W(x,i)\\ W(y,i+1) - W(x,i+1) & \gt W(y,i) - W(x,i) \\ W(y,i+1) - W(y,i) & \gt W(x,i+1) - W(x,i) \\ \end{aligned} \]

这显然是矛盾的,因为 \(y<x\)\(W(y,i+1) - W(y,i)\) 相当于 \(\{B_1,B_2,\dots,B_x\}\) 中的第 \(i+1\) 大的元素,但是 \(\{B_1,B_2,\dots,B_y\} \subset \{B_1,B_2,\dots,B_x\}\),于是必有 \(W(y,i+1) - W(y,i) \le W(x,i+1) - W(x,i)\),矛盾。

因此,\(f\) 是非严格单调递增的。

其他例题