0x02 内容总结与例题习题

本文介绍了递推与递归的基本概念及应用,通过多个例题深入浅出地讲解了如何利用递推与递归解决实际问题。文章还探讨了分形问题的递归解决方法,并提供了详细的AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!

内容总结

递推与递归的宏观描述

对于一个待求解的问题,当它局限在某处边界、某个小范围或者某种特殊情形下时,其答案往往是已知的。如果能够将该解答的应用场景扩大到原问题的状态空间,并且扩展过程的每个步骤具有相似性,就可以考虑使用递推或者递归求解。

简单应用

话不多说,直接看题。

【例题】费解的开关(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]=2d[i1]+1。我们把三座塔称为 A 、 B 、 C A、B、C ABC(起始状态 i i i个盘子都在 A A A),则这个递推式表示的是我们先把 i − 1 i - 1 i1个盘子移动到 B B B(最少需要 d [ i − 1 ] d[i - 1] d[i1]步,然后把1个盘子移动到 C C C(需要一步),再把 B B B上的一个盘子移动到 C C C(最少需要 d [ i − 1 ] d[i - 1] d[i1]步)。
回到本题,我们类似地设 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(2f[j]+d[ij]),其中 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 N1级城市按照一定的规律顺时针拼接而成。记 c n t ( N ) cnt(N) cnt(N)为N级城市的房屋数量,由题意得, c n t ( N ) = 2 2 ∗ n cnt(N) = 2 ^{2 * n} cnt(N)=22n。则编号为 M M M的房屋在 N − 1 N - 1 N1级城市中的编号就为 M % c n t ( N − 1 ) M \% cnt(N - 1) M%cnt(N1)。那么我们可以递归求出 M M M N − 1 N - 1 N1级城市中的位置 ( x , y ) (x,y) (x,y)
这时我们还需要知道 M M M N N N级城市哪一块 N − 1 N - 1 N1城市里面,显然令 z = M / c n t ( N − 1 ) z = M / cnt(N - 1) z=M/cnt(N1)就可以得到实在哪一块啦。
那么分为下面四种情况:
z = 0 z = 0 z=0时,说明在左上角那一块。左上角由 N − 1 N - 1 N1级城市顺时针旋转 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) (2leny1,lenx1)
可以利用坐标变换公式自己推一推!
( 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值