《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!
内容总结与例题
二进制状态压缩
有一些问题经常可以采用二进制状态压缩起来成为一个容易解决的问题。
常用的操作有:
取出整数
n
n
n在二进制表示下的第
k
k
k位:(n >> k) & 1
把整数
n
n
n在二进制表示下的第
k
k
k位取反:n ^ (1 << k)
对整数
n
n
n在二进制表示下的第
k
k
k位赋值1:n | (1 << k)
对整数
n
n
n在二进制表示下的第
k
k
k位赋值0:n & (~(1 << k))
【例题】最短Hamilton路径(AcWing91)
题目链接
思路: 观察到
n
n
n的取值范围只有
(
n
<
=
20
)
(n <= 20)
(n<=20),我们可以往暴力的方面想。由于每个点都只能经过一次,我们可以直接枚举点被经过的先后顺序,但时间复杂到高达
O
(
n
∗
n
!
)
O(n*n!)
O(n∗n!)。其实这道题是属于状态压缩dp的,之后在更新0x56章节时会着重讲到。利用dp的思想,我们可以令
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示点被经过的状态为
i
i
i,最后经过的点为
j
j
j时,所需的最短路径长度。那么可以得到以下转移方程:
f
[
i
]
[
j
]
=
m
i
n
{
f
[
i
x
o
r
(
1
<
<
j
)
]
[
k
]
+
w
e
i
g
h
t
(
k
,
j
)
}
f[i][j] = min \{f[i xor (1 << j)][k] + weight(k,j)\}
f[i][j]=min{f[ixor(1<<j)][k]+weight(k,j)},其中
0
<
=
k
<
n
0 <= k < n
0<=k<n并且
(
(
i
>
>
j
)
&
1
)
=
=
1
((i >> j) \& 1) == 1
((i>>j)&1)==1
这个方程表示的是:对于当前节点
j
j
j,由于每个节点只能被恰好经过一次,那么之前一定没有别经过。那么当前状态
f
[
i
]
[
j
]
f[i][j]
f[i][j]就可以从“点被经过的状态中
j
j
j不存在”的状态
f
[
i
x
o
r
(
1
<
<
j
)
]
[
k
]
f[ixor(1 << j)][k]
f[ixor(1<<j)][k]转移过来,加上
k
k
k到
j
j
j的距离就好啦
AC代码:
#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f
#define N (1 << 20)
using namespace std;
int n;
int a[21][21];
int f[N][21];
void solve(){
scanf("%d",&n);
for(int i = 0;i < n;i ++)
for(int j = 0;j < n;j ++)
scanf("%d",&a[i][j]);
memset(f,0x3f,sizeof(f));
f[1][0] = 0;
for(int i = 0;i < (1 <<n);i ++){
for(int j = 0;j < n; j++){
if(i & ( 1 << j )){
for(int k = 0;k <n;k ++){
if((i ^ (1 << j)) & ( 1 << k )){
f[i][j] = min(f[i][j], f[(i ^ (1 << j))][k] + a[k][j]);
}
}
}
}
}
printf("%d\n",f[(1 << n) - 1][n - 1]);
}
int main(){
solve();
return 0;
}
lowbit运算
l
o
w
b
i
t
(
n
)
lowbit(n)
lowbit(n)定义为非负整数
n
n
n在二进制表示下“最低位的1及其后边的所有的0”构成的数值。他被应用于树状数组中(在第0x42节会详细介绍)。
计算公式为: lowbig(n) = n & -n
自己推一推就知道什么意思了
习题
飞行员兄弟(AcWing116)
题目链接
思路: 观察到是一个
4
×
4
4×4
4×4的矩阵,那么我们直接枚举拨动开关的所有情况共
(
1
<
<
16
)
(1 << 16)
(1<<16)种,对于每一个情况进行开关的变换,判断最终是否所有开关全部打开。若全部打开则更新最小答案。
AC代码:
#include<bits/stdc++.h>
#define N 1005
using namespace std;
char a[5][5];
int b[5][5],c[5][5];
void change(int x,int y){
c[x][y] ^= 1;
for(int i = 1;i <= 4;i ++){
if(i != x) c[i][y] ^= 1;
if(i != y) c[x][i] ^= 1;
}
}
bool check(){
for(int i = 1;i < 5;i ++)
for(int j = 1;j < 5;j ++)
if(c[i][j] == 0) return false;
return true;
}
bool cmp(pair<int,int> a,pair<int,int> b){
if(a.first == b.first)
return a.second < b.second;
return a.first < b.first;
}
void solve(){
for(int i = 1;i <= 4;i ++)
scanf("%s",a[i] + 1);
for(int i = 1;i <= 4;i ++)
for(int j =1;j <= 4;j ++)
if(a[i][j] == '-')
b[i][j] = 1;
else b[i][j] = 0;
int ans = 100;
vector<pair<int,int> > anss;
for(int i = 0;i < (1 << 16);i ++){
memcpy(c,b,sizeof(b));
int cnt = 0;
vector<pair<int,int> > tmp;
for(int j = 0;j < 16;j ++){
if(i & ( 1 << j)){
cnt ++;
int x = j / 4,y = j % 4;
change(x + 1,y + 1);
tmp.push_back(make_pair(x + 1,y + 1));
}
}
if(check() && cnt < ans){
ans = cnt;
anss = tmp;
}
}
cout << ans << endl;
sort(anss.begin(),anss.end(),cmp);
for(auto x : anss)
cout << x.first << " " << x.second << endl;
}
int main(){
solve();
return 0;
}