递推与递归
《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!
内容总结
递推与递归的宏观描述
对于一个待求解的问题,当它局限在某处边界、某个小范围或者某种特殊情形下时,其答案往往是已知的。如果能够将该解答的应用场景扩大到原问题的状态空间,并且扩展过程的每个步骤具有相似性,就可以考虑使用递推或者递归求解。
简单应用
话不多说,直接看题。
【例题】费解的开关(AcWing95)
题目链接
思路: 通过观察我们不难发现三个性质。
1.每个位置至多被点击一次
2.若固定了第一行,则满足题意的点击方案至多只有一种
3.点击的先后顺序不影响最终结果
所以我们只需要枚举第一行的所有点击状态,就可以覆盖所有的点击情况了。固定了第一行的状态之后,我们只需要从第二行开始一行一行往下递推,最终如果棋盘可以全部变为0则更新答案。
AC代码:
#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f
#define N 6
using namespace std;
char a[N][N],b[N][N];
void f(char& x){
if(x == '0') x = '1';
else x = '0';
}
void change(int x,int y){
f(b[x][y]);
if(x > 0) f(b[x - 1][y]);
if(x < 4) f(b[x + 1][y]);
if(y < 4) f(b[x][y + 1]);
if(y > 0) f(b[x][y - 1]);
}
void solve(){
for(int i = 0;i < 5;i ++)
scanf("%s",&a[i]);
int ans = 10;
for(int i = 0;i < 32;i ++){
memcpy(b,a,sizeof(a));
int tmp = 0;
for(int j = 0;j < 5;j ++){
if(i & (1 << j))
change(0,j),tmp ++;
}
for(int j = 1;j < 5;j ++){
for(int k = 0;k < 5;k ++){
if(b[j - 1][k] == '0')
change(j,k),tmp ++;
}
}
int fl = 1;
for(int j = 0;j < 5;j ++)
if(b[4][j] == '0'){
fl = 0;
break;
}
if(fl){
ans = min(ans,tmp);
}
}
if(ans > 6) puts("-1");
else printf("%d\n",ans);
}
int main(){
int t;
scanf("%d",&t);
while(t --)
solve();
return 0;
}
【例题】奇怪的汉诺塔(AcWing96)
题目链接
思路: 我们先考虑
n
n
n个盘子
3
3
3座塔的情况。设
d
[
i
]
d[i]
d[i]为求解
i
i
i个盘子3座塔时所需的最小步数,则有
d
[
i
]
=
2
∗
d
[
i
−
1
]
+
1
d[i] = 2 * d[i - 1] + 1
d[i]=2∗d[i−1]+1。我们把三座塔称为
A
、
B
、
C
A、B、C
A、B、C(起始状态
i
i
i个盘子都在
A
A
A),则这个递推式表示的是我们先把
i
−
1
i - 1
i−1个盘子移动到
B
B
B(最少需要
d
[
i
−
1
]
d[i - 1]
d[i−1]步,然后把1个盘子移动到
C
C
C(需要一步),再把
B
B
B上的一个盘子移动到
C
C
C(最少需要
d
[
i
−
1
]
d[i - 1]
d[i−1]步)。
回到本题,我们类似地设
f
[
i
]
f[i]
f[i]为求解
i
i
i个盘子4座塔时所需的最小步数,则类似的有
f
[
i
]
=
m
i
n
(
2
∗
f
[
j
]
+
d
[
i
−
j
]
)
f[i] = min(2 * f[j] + d[ i - j ])
f[i]=min(2∗f[j]+d[i−j]),其中
1
<
=
j
<
n
1 <= j < n
1<=j<n,自己思考一下这个式子的含义吧。
AC代码:
#include<bits/stdc++.h>
using namespace std;
int d[20],f[20];
int main(){
d[1] = 1;
for(int i = 2;i <= 12;i ++)
d[i] = 2 * d[i - 1] + 1;
memset(f,0x3f,sizeof(f));
f[1] = 1;
for(int i = 2;i <= 12;i ++)
for(int j = 1;j < 12;j ++)
f[i] = min(f[i],2 * f[j] + d[i - j]);
for(int i = 1;i <= 12;i ++)
cout << f[i] << endl;
return 0;
}
分形
这是个有意思的递归问题。
直接上题目
【例题】分形之城(AcWing98)
题目链接
思路: 本题的关键是求出编号为
M
M
M(编号从0开始)的房屋在
N
N
N级城市中的位置。把该问题记为:
c
a
l
c
(
N
,
M
)
calc(N,M)
calc(N,M),本题要求的就是
c
a
l
c
(
N
,
S
)
calc(N,S)
calc(N,S)与
c
a
l
c
(
N
,
D
)
calc(N,D)
calc(N,D)之间的距离。由题意我们可以知道
N
N
N级城市由4个
N
−
1
N - 1
N−1级城市按照一定的规律顺时针拼接而成。记
c
n
t
(
N
)
cnt(N)
cnt(N)为N级城市的房屋数量,由题意得,
c
n
t
(
N
)
=
2
2
∗
n
cnt(N) = 2 ^{2 * n}
cnt(N)=22∗n。则编号为
M
M
M的房屋在
N
−
1
N - 1
N−1级城市中的编号就为
M
%
c
n
t
(
N
−
1
)
M \% cnt(N - 1)
M%cnt(N−1)。那么我们可以递归求出
M
M
M在
N
−
1
N - 1
N−1级城市中的位置
(
x
,
y
)
(x,y)
(x,y)。
这时我们还需要知道
M
M
M在
N
N
N级城市哪一块
N
−
1
N - 1
N−1城市里面,显然令
z
=
M
/
c
n
t
(
N
−
1
)
z = M / cnt(N - 1)
z=M/cnt(N−1)就可以得到实在哪一块啦。
那么分为下面四种情况:
当
z
=
0
z = 0
z=0时,说明在左上角那一块。左上角由
N
−
1
N - 1
N−1级城市顺时针旋转
90
°
90°
90°之后,水平翻转得到。由坐标变换公式可以得到
M
M
M在
N
N
N级城市中的坐标为
(
y
,
x
)
(y,x)
(y,x)
当
z
=
1
z = 1
z=1时,坐标为
(
x
,
y
+
l
e
n
)
(x,y + len)
(x,y+len)
当
z
=
2
z = 2
z=2时,坐标为
(
x
+
l
e
n
,
y
+
l
e
n
)
(x + len,y + len)
(x+len,y+len)
当
z
=
3
z = 3
z=3时,坐标为
(
2
∗
l
e
n
−
y
−
1
,
l
e
n
−
x
−
1
)
(2 * len - y - 1,len - x - 1)
(2∗len−y−1,len−x−1)
可以利用坐标变换公式自己推一推!
(
x
,
y
)
(x,y)
(x,y)顺时针旋转
θ
θ
θ角度得到的坐标为:
AC代码:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL a,b;
int n;
long double dis(pair<LL,LL> p1,pair<LL,LL> p2){
return 10 * sqrt((p1.first - p2.first) * (p1.first - p2.first) + (p1.second - p2.second) * (p1.second - p2.second));
}
pair<LL,LL> cal(int n, LL m){
if(n == 0) return make_pair(0,0);
LL len = 1LL << (n - 1),cnt = 1LL << (2* n - 2);
pair<LL,LL> pos = cal(n - 1,m % cnt);
LL x = pos.first,y = pos.second;
LL z = m / cnt;
if(z == 0) return make_pair(y,x);
else if(z == 1) return make_pair(x,y + len);
else if(z == 2) return make_pair(x + len,y + len);
else if(z == 3) return make_pair(2 * len - y - 1,len - x - 1);
}
void solve(){
scanf("%d%lld%lld",&n,&a,&b);
pair<LL,LL> p1 = cal(n,a - 1);
pair<LL,LL> p2 = cal(n,b - 1);
printf("%lld\n",(LL)(dis(p1,p2) + 0.5));
}
int main(){
int t;scanf("%d",&t);
while(t --)
solve();
return 0;
}
习题
【练习】分形(AcWing118)
题目链接
思路: 这题和例题很像,也是定位坐标,但是比较简单,就不过多赘述了。就是递归下去找到每一个X的位置标记一下就好啦。
AC代码:
#include<bits/stdc++.h>
#define N 5005
using namespace std;
int n;
int a[N][N];
void dfs(int n,int x,int y){
if(n == 1){
a[x][y] = 1;
return;
}
int len = pow(3,n - 2);
dfs(n - 1,x - len,y - len);
dfs(n - 1,x - 2 * len,y);
dfs(n - 1,x,y - 2 * len);
dfs(n - 1,x,y);
dfs(n - 1,x - 2 * len,y - 2 * len);
}
void solve(){
dfs(n,pow(3,n - 1),pow(3,n - 1));
for(int i = 1;i <= pow(3,n - 1);i ++)
{
for(int j = 1;j <= pow(3,n - 1);j ++)
{
if(a[i][j]) putchar('X');
else putchar(' ');
}
puts("");
}
puts("-");
}
int main(){
while(scanf("%d",&n) && n != -1)
solve();
return 0;
}
【练习】袭击(AcWing119)
思路: 平面最近点对模板题
AC代码:
#include<bits/stdc++.h>
#define N 200005
using namespace std;
const double INF = 1e10;
struct Point{
double x,y;
bool type;
bool operator <(const Point& b)const{
return x < b.x;
}
bool operator == (const Point& b)const{
return x == b.x && y == b.y && type == b.type;
}
}p[N],tmp[N];
double dist(Point& a,Point& b){
if(a.type == b.type) return INF;
double dx = a.x - b.x,dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
double dfs(int l,int r){
if(l >= r){
return INF;
}
// printf("l = %d r = %d\n",l,r);
int mid = (l + r) >> 1;
double midx = p[mid].x;
double res = min(dfs(l,mid),dfs(mid + 1,r));
{
int i = l,j = mid + 1,k = l;
while(i <= mid && j <= r){
if(p[i].y <= p[j].y) tmp[k ++] = p[i ++];
else tmp[k ++] = p[j ++];
}
while(i <= mid) tmp[k ++] = p[i ++];
while(j <= r) tmp[k ++] = p[j ++];
for(i = l;i <= r;i ++) p[i] = tmp[i];
}
int cnt = 0;
for(int i = l;i <= r;i ++)
if(p[i].x >= midx - res && p[i].x <= midx + res)
tmp[++ cnt] = p[i];
for(int i = 1;i <= cnt;i ++){
int tmpcnt = 0;
for(int j = i - 1;j >= 1 && tmp[i].y - tmp[j].y < res;j --){
res = min(res,dist(tmp[i],tmp[j])),tmpcnt ++;
}
}
return res;
}
int main(){
// freopen("in.txt","r",stdin);
int T;scanf("%d",&T);
while(T --){
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
scanf("%lf%lf",&p[i].x,&p[i].y),p[i].type = 0;
for(int i = n + 1;i <= 2 * n;i ++)
scanf("%lf%lf",&p[i].x,&p[i].y),p[i].type = 1;
sort(p + 1,p + 1 + 2 * n);
int len = unique(p + 1,p + 1 + 2 * n) - p - 1;
printf("%.3f\n",dfs(1,len));
}
return 0;
}