《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!
KMP模式匹配
KMP的原理以及 n e x t next next数组的求解方法这里就不再赘述啦!主要讲讲他们的应用吧!
【例题】周期(AcWing141)
题目链接
思路: 首先我们的结论是:如果
i
i%(i-next[i]) == 0
i时,
i
−
n
e
x
t
[
i
]
i-next[i]
i−next[i]是前缀
i
i
i的最小循环节长度。证明起来是比较简单的,自己可以画个图证明一下。那么我们求出
n
e
x
t
next
next数组之后,就枚举每一个位置,求出来就好啦!
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000005;
char s[N];
int n;
int nt[N];
int kase;
void getnt(){
nt[0] = -1;
int i = 0,j = -1;
while(i < n){
if(j == -1 || s[i] == s[j]) nt[++ i] = ++ j;
else j = nt[j];
}
}
void solve(){
scanf("%s",s);
getnt();
printf("Test case #%d\n",++ kase);
for(int i = 1;i <= n;i ++){
if(nt[i] && i % (i - nt[i]) == 0){
printf("%d %d\n",i, i / (i - nt[i]));
}
}
puts("");
}
int main(){
while(scanf("%d",&n) && n)
solve();
return 0;
}
【习题】奶牛矩阵(AcWing159)
题目链接
思路: 观察到矩形的宽度数据范围很小,我们可以枚举矩形的宽度,暴力判断每一个宽度在每一行是否满足覆盖条件。对于满足条件的宽度,我们可以用KMP来求出按列的最小循环节长度,注意这里的判断相等不是判断单字符相等,而是判断长度为所枚举的宽度的字符串相等。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 10005;
const int INF = 0x3f3f3f3f;
int n,m;
char s[N][80];
int nt[N];
bool fl[80];
bool equal(int i,int j,int width){
for (int k = 1;k <= width;k ++)
if(s[i][k] != s[j][k]) return false;
return true;
}
void getnt(int width){
nt[1] = 0;
int i = 1,j = 0;
while(i <= n){
if(j == 0 || equal(i,j,width)) nt[++ i] = ++ j;
else j = nt[j];
}
}
void solve(){
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i ++)
scanf("%s", s[i] + 1);
memset(fl, true, sizeof fl);
for(int i = 1;i <= n;i ++){
for(int len = 1;len <= m;len ++){
if(fl[len]){
for(int j = len + 1;j <= m;j += len){
for(int k = 1;k <= len && j + k - 1 <= m;k ++)
if(s[i][k] != s[i][j + k - 1]){
fl[len] = false;
break;
}
if(!fl[len]) break;
}
}
}
}
int ans = INF;
for(int len = 1;len <= m;len ++){
if(fl[len]){
getnt(len);
ans = min(ans, len * (n - nt[n + 1] + 1));
}
}
printf("%d\n", ans);
}
int main(){
solve();
return 0;
}
【习题】匹配统计(AcWing160)
题目链接
思路: 我们不难用字符串哈希在
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)时间内算出所有结果。
这里介绍一个使用
K
M
P
KMP
KMP的方法在
O
(
n
)
O(n)
O(n)时间内求解问题。
令主串为
S
S
S,模式串为
T
T
T,
f
[
i
]
f[i]
f[i]表示匹配长度至少为
i
i
i的后缀个数。
我们知道,
K
M
P
KMP
KMP在匹配的时候,指向主串的指针是不回溯的,当该指针指向的位置为
i
i
i,模式串指针指向的位置为
j
j
j时,如果
S
[
i
]
=
=
T
[
j
]
S[i] == T[j]
S[i]==T[j],那么,后缀
i
−
j
i - j
i−j的匹配长度至少为
j
j
j,此时
f
[
j
]
+
+
f[j]++
f[j]++。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, m, q;
char s[N],t[N];
int nt[N];
int f[N];
void getnt(char s[],int n){
int i = 0,j = -1;
nt[0] = -1;
while(i < n){
if(j == -1 || s[i] == s[j]) nt[++ i] = ++ j;
else j = nt[j];
}
}
void KMP(){
int i = 0,j = 0;
while(i < n){
if(j == -1 || s[i] == t[j]) i ++, j ++, f[j] ++;
else j = nt[j];
}
}
void solve(){
scanf("%d%d%d", &n, &m, &q);
scanf("%s",s);
scanf("%s",t);
getnt(t,m);
KMP();
for(int i = m; i; i --) f[nt[i]] += f[i];
while(q --){
int x;
scanf("%d", &x);
printf("%d\n",f[x] - f[x + 1]);
}
}
int main(){
solve();
return 0;
}
最小表示法
假如我们的字符串为
S
[
1...
n
]
S[1...n]
S[1...n]
我们先规定
B
[
i
]
B[i]
B[i]为从
i
i
i开始的循环同构串,即
S
[
i
.
.
.
n
]
S[i...n]
S[i...n]+
S
[
1...
i
−
1
]
S[1...i-1]
S[1...i−1]
最小表示法
O
(
n
)
O(n)
O(n)算法如下:
1.首先把字符串复制一遍放在后面
2.初始化为
i
=
1
,
j
=
2
i=1,j=2
i=1,j=2
3.通过直接向后扫描的方法,比较
B
[
i
]
B[i]
B[i]与
B
[
j
]
B[j]
B[j]两个循环同构串。
(1)如果扫描了
n
n
n个字符之后仍然相等,说明
S
S
S有更小的循环元,并且该循环元已经扫描完毕,此时
B
[
m
i
n
(
i
,
j
)
]
B[min(i,j)]
B[min(i,j)]就是最小表示,算法结束。
(2)如果在
i
+
k
i + k
i+k与
j
+
k
j + k
j+k处发现不相等的字符:
若
S
[
i
+
k
]
>
S
[
j
+
k
]
S[i + k] > S[j + k]
S[i+k]>S[j+k],那么令
i
=
i
+
k
+
1
i= i + k + 1
i=i+k+1。若此时
i
=
j
i = j
i=j,令
i
=
i
+
1
i = i + 1
i=i+1。
若
S
[
i
+
k
]
<
S
[
j
+
k
]
S[i + k] < S[j + k]
S[i+k]<S[j+k],那么令
j
=
j
+
k
+
1
j = j + k + 1
j=j+k+1。若此时
i
=
j
i = j
i=j,令
j
=
j
+
1
j = j + 1
j=j+1。
4.执行步骤(3)直到
i
>
n
i > n
i>n或者
j
>
n
j > n
j>n时,停止算法。此时
B
[
m
i
n
(
i
,
j
)
]
B[min(i, j)]
B[min(i,j)]为最小表示。
代码实现见例题。
【例题】雪花雪花雪花(AcWing137)
题目链接
思路: 取正序和逆序最小表示中的最小表示,如果相等,则为同一雪花。
AC代码:
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n;
int a[N][6];
int idx[N];
bool cmp_a(int a[],int b[]){ // a < b
for(int i = 0,j = 0; i < 6; i ++, j ++){
if(a[i] < b[j]) return true;
else if(a[i] > b[j]) return false;
}
return false;
}
bool cmp2(int x,int y){
return cmp_a(a[x],a[y]);
}
void get_min(int a[]){
static int b[12];
for(int i = 0;i < 12;i ++) b[i] = a[i % 6];
int i = 0,j = 1,k;
while(i < 6 && j < 6){
for(k = 0;k < 6 && b[i + k] == b[j + k];k ++);
if(k == 6) break;
if(b[i + k] < b[j + k]){
j += k + 1;
if(i == j) j ++;
}
else{
i += k + 1;
if(i == j) i ++;
}
}
k = min(i,j);
for(i = 0;i < 6;i ++) a[i] = b[i + k];
}
void solve(){
scanf("%d",&n);
for(int i = 1;i <= n;i ++){
int snow[6],isnow[6];
for(int j = 0, k = 5;j < 6;j ++,k -- ){
scanf("%d",&snow[j]);
isnow[k] = snow[j];
}
get_min(snow);
get_min(isnow);
if(cmp_a(snow,isnow)) memcpy(a[i],snow,sizeof snow);
else memcpy(a[i], isnow, sizeof isnow);
idx[i] = i;
}
sort(idx + 1,idx + 1 + n,cmp2);
bool flag = false;
for(int i = 2;i <= n;i ++){
if(!cmp_a(a[idx[i]], a[idx[i - 1]]) && !cmp_a(a[idx[i - 1]], a[idx[i]]) )
{
flag = true;
}
}
if(!flag) puts("No two snowflakes are alike.");
else puts("Twin snowflakes found.");
}
int main(){
solve();
return 0;
}