算法2022 · 递归与分治

字符全排列测试+去重思想

//【字符串の全排列】·去重
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int t = 0, n; //计数器,字符串长度-1
char str[N];

bool pd(int k, int i)
{ //【思想】区间内不能出现相同的元素
    for (int x = k; x < i; x++)
        if (str[x] == str[i])
            return false;
    return true;
}
void all(int k) //当前位置k
{
    if (k == n) //当前位置抵达最后q
    {
        printf("%d\t:%s\n", ++t, str);
        return; //向下探底
    }

    for (int i = k; i <= n; i++) //依次和后面的元素交换(n=len-1)
    {
        if (pd(k, i)) // ki是否可以交换【ki之间不能出现和i相同的元素】
        {
            swap(str[k], str[i]); //交换
            all(k + 1);           //递归
            swap(str[k], str[i]); //恢复
        }
    }
}

int main()
{
    scanf("%s", &str);
    n = strlen(str) - 1;
    all(0);
    return 0;
}

概要:指针+字符串 具有一定优势

        允许用=号进行赋值,用指针a[123]来引导定位字符串中单个字符

重要思想:【去重】

        假若k与i意图交换位置(以下用[a,b]表示,a在左侧)

        要求:b左侧【所有字符】不允许出现与【位置b】相同的【字符】

因为假若出现,在之前的递归过程中,已经出现了此次交换的结果

注意!此处b在层层递归过程中,是从a出发,逐个增大到m

        层数=k的,例如13交换就是第一层,34交换是第三层,44交换到顶了就输出

举例子:abbc   意图交换1 3位置

        如果没有去重:交换13,产生bbac

事实上,在之前第一层12交换后,产生babc;进入下一层,再2与3交换,出现bbac

        这就是反例,举例子利于理解

整数划分问题

//【整数の加法分解】
#include <iostream>
#include <stdio.h>
using namespace std;

int get(int n, int m) // n:拆分后的数字和   m:最大加数
{
    if (n == 1 || m == 1)                 //【总数】=1 或 【最大加数】=1
        return 1;                         //只有一种情况 1=1 或 4=1+1+1+1
    if (n < 1 || m < 1)                   //【总数】不是【正整数】,或 【最大加数】不是【正整数】
        return 0;                         //不合法,返回0
    if (m > n)                            //【最大加数】>【总数】
        return get(n, n);                 // 最大加数不可能超过总数
    if (n == m)                           //如果【加数】=【总数】
        return 1 + get(n, n - 1);         // 4=4算1种,加上(小于)4的情况
    return get(n, m - 1) + get(n - m, m); //其他常规情况
    // 17划4,拆成4+ 3+ 2+ 1+
    //左侧get负责3+ 2+ 1+模块,右侧get负责4+模块
    //左侧相当于【除去4(最大加数=3)】条件下,n的划分
    //右侧相当于【固定一项4(挖去)】,剩下部分做【最大加数≤4】的分配
}
int main()
{
    int n;
    scanf("%d", &n);
    printf("%d", get(n, n));
    return 0;
}
//一个正整数拆成多个正整数之和,有几种拆分方法?

//关于其他情况的部署,举例子q(17,4)
//可以分解成4+ 3+ 2+ 1+ 共4个模块
//[左侧]:q(17,3)    囊括右侧 3+ 2+ 1+三大模块
//[右侧]:q(13,4)    囊括左侧 4+ 这唯一の模块
//【产生原因】13是把左侧4+给挖去(17-4)后,剩下部分开展(以最大加数=4为限制)的划分
//产生划分的个数和没有失去4+之前一样
//剩余总数=13, 继续进行max=4的划分
//【本质】:最后结果和4+模块的分解个数是完全一致的
//右侧表达式的思想是把 第一列4+去掉后,剩余部分继续进行分解
//而最大值max依旧=4

重要的算法思想,比较新奇,是递归的一部分,写一写我的理解与心得

对于q(n,m),我的理解是:【总数n】,【加数最大值m】max

        要求1:分解最小单位是1,不允许出现0
                出现则返回0,不统计数据

        要求2:n=1或m=1(总数就=1   或者  分解的最大加数=1)

                出现则返回1(只有1中划分可能)          1=1   或  n=1+1+1+1(n个1)

        要求3:m>n说明加数最大值比总数还大

                (比如总数7,我允许你最大加数=13,事实上我最大只能到7)

                此时,返回q(n,n)                反璞归真

        要求4:m=n   这下真的反璞归真了,得给他往下拆,拆成1+(n-1)

                返回q(n,n)=q(n,n-1)+1

        要求5:n>m   (例如,总数=17,允许最大=14)可以拆成14+ 13+ 12+多个模块

                返回q(n,m)=q(n,m-1)+q(n-m,m)

 为什么会这样呢,这似乎非常抽象令人费解,在我细心研究后,有以下看法

举例子:q(17,4)=q(17,3)+q(13,4)

        这个要如何理解呢

【左侧公式】:可以划分成 4+ 3+ 2+ 1+ 共4个模块

【右侧17-3】:收割了3+ 2+ 1+共3个模块

        这里是【向下递归】,寻找【总数=17】、【最大加数=3】的情况

        也就是要找出3+ 2+ 1+的各种情况

【右侧13-4】:收割了4+ 共1个模块

        这里是对左侧4+模块进行了一次变形

        是曲线救国的一个过程

        【思考】:代码操作中,4+模块的第一个【4+】被删去

        全公式代码-4  ==13      之后对【剩余部分】开展【最大加数=4】的划分

        【本质上】:最后统计出来的个数和【没有删除4+】是完全一样的

        把所有公式整齐列出,砍去第一个4+后,剩余总数13 分解max=4

        这样就可以算出原生4+模块公式的个数

汉诺塔(n盘3柱)

#include <iostream>
#include <stdio.h>
using namespace std;
int t = 0;
void hanoi(int n, int a, int b, int c)
{ //共n偏盘片,从a-->b,借助c
    if (n > 0)
    {
        hanoi(n - 1, a, c, b);//递归
        printf("%d:%d-->%d\n", ++t, a, b);//实际移动
        hanoi(n - 1, c, b, a);//递归
    }
}

int main()
{
    int n; //从a到b,以c为辅助
    printf("汉诺塔(n盘片,3柱体),请输入n:");
    scanf("%d", &n);
    hanoi(n, 1, 2, 3);
    return 0;
}
/*
【思想】
一共n个盘片,从a移动到b,借助c
    将n-1片,从a        移动到【辅助塔c】
    将【底片】,从a     移动到【目标塔b】
    将n-1片,从c       移动到【目标塔b】
*/

棋盘覆盖

//【P20 2.6棋盘覆盖】
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
const int N = 1e4 + 7;
int num = 0, map[N][N], n;
void cover(int x, int y, int hx, int hy, int size)
{
    if (size == 1) //自棋盘大小=1,没得玩
        return;
    int mid = size >> 1;
    int cx = x + mid, cy = y + mid;
    int t = ++num;
    // t:第几个部署的三角块
    // mid:规模半值                      8
    // xy系列:全局边界(左上点)          0 0
    // h系列:全局边界(右下点)           16  16
    // c系列:中间点(右下块的左上点)      8   8
    //触发3个else和一个if(黑点方向)

    //【黑点坐标·左上块】
    if (hx < cx && hy < cy)
        cover(x, y, hx, hy, mid); //部分递归
    else
    {
        map[cx - 1][cy - 1] = t;          //染色
        cover(x, y, cx - 1, cy - 1, mid); //完全递归
    }
    //【黑点坐标·右上块】
    if (hx < cx && hy >= cy)
        cover(x, cy, hx, hy, mid);
    else
    {
        map[cx - 1][cy] = t;
        cover(x, cy, cx - 1, cy, mid);
    }
    //【黑点坐标·左下块】
    if (hx >= cx && hy < cy)
        cover(cx, y, hx, hy, mid);
    else
    {
        map[cx][cy - 1] = t;
        cover(cx, y, cx, cy - 1, mid);
    }
    //【黑点坐标·右下块】
    if (hx >= cx && hy >= cy)
        cover(cx, cy, hx, hy, mid);
    else
    {
        map[cx][cy] = t;
        cover(cx, cy, cx, cy, mid);
    }
}
void show()
{
    for (int x = 0; x < n; x++)
    {
        for (int y = 0; y < n; y++)
            printf("%d\t", map[x][y]);
        printf("\n\n\n");
    }
}
int main()
{
    int x, y;
    scanf("%d%d%d", &n, &x, &y);
    cover(0, 0, x - 1, y - 1, n); //起点,黑点,规模
    show();
    return 0;
} //注意!n必须是2的k次方

循环赛 日程表

//【循环赛日程表P35 2.11】
#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 1e4 + 7;
int a[N][N]; //总表
int mid;     //【四分块】半径
int n;       //【小幂块】个数
int all;     //【全块】半径
int mi;      //阶数(幂)
int sum;     // show使用
void show()
{
    printf("[%d]\n", ++sum);
    for (int x = 1; x <= all; x++)
    {
        for (int t = 1; t <= all; t++)
            printf("%d\t", a[x][t]);
        printf("\n\n\n");
    }
    printf("\n\n");
    system("pause");
    printf("\n\n");
}
void init()
{
    scanf("%d", &mi);
    n = mid = 1;
    for (int x = 1; x < mi; x++)
        n *= 2;
    all = n * 2;
    for (int x = 1; x <= all; x++)
        a[1][x] = x;
    printf("初始态:n=%d  t=%d  第一行部署12345678\n", n, all);
}
int main()
{
    //【小幂块】拆成4个【四分块】
    //每次行部署完成,【小幂块】半径*2,【四分块】半径*2
    //【小幂块】个数/2
    init();
    for (int x = 0; x < mi; x++) // 总轮次,每轮结束,处理完成 2 4 8 行
    {
        for (int t = 0; t < n; t++) //扫描【小幂块】(偏移 0 1 2 3)
        {
            for (int i = mid + 1; i <= 2 * mid; i++)
            { //【四分块】各点扫描(右下块)
                for (int j = mid + 1; j <= 2 * mid; j++)
                {
                    int y = t * mid * 2 + j;  //偏移,得出真实的y
                    int sx = i - mid;         //上行【四分块】的行数
                    a[i][y] = a[sx][y - mid]; //右下=左上
                    a[i][y - mid] = a[sx][y]; //左下=右上
                }
            }
        } //【小幂块】个数/2,【四分块】半径*2
        show(), n /= 2, mid *= 2;
    }
    return 0;
}

根号划分

#include <stdio.h>
#include <math.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define s(a, l, r) sort(a + l, a + r + 1)
#define g(a, l, r) sort(a + l, a + r + 1, greater<int>())
const int N = 1e6 + 7;
int a[N], b[N], n;
void show()
{
    for (int x = 0; x < n; x++)
        printf("%d ", a[x]);
    printf("\n");
}
void merge(int *a, int l, int r)
{
    if (l >= r)
        return;
    int t = (int)sqrt(l - r + 1);
    if (t > 1)
    {
        for (int x = 0; x < t; x++)
            merge(a, l + x * t, l + (x + 1) * t);
        merge(a, l + t * t, r);
        //注意!末尾特判,因为可能不够凑出整个sqrt
    }           //实践过程使用STL武器库,
    s(a, l, r); //改成g就是从大到小排序
}
int main()
{
    scanf("%d", &n);
    for (int x = 0; x < n; x++) //从1开始存
        scanf("%d", &a[x]);
    merge(a, 0, n - 1), show();
    return 0;
}

/*
16
58 96 24 75 15 85 74 69 12 54 87 50 65 32 84 61

19
58 96 24 75 525 15 85 74 274 69 12 54 233 87 50 65 32 84 61
*/

整数因子分解

#include <iostream>
#include <stdio.h>
int res = 0, n;
void asd(int n)
{
    if (n == 1)
        res++;
    for (int i = 2; i <= n; i++)
        if (n % i == 0) //可以整除就向下分解
            asd(n / i); //全排列过程
}

int main()
{
    scanf("%d", &n), asd(n);
    printf("%d", res);
    return 0;
}

众数问题

#include <iostream>
#include <cstring>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int a[N];
int zs, num;
void range(int *a, int n, int &l, int &r)
{
    int mid = n / 2;
    for (l = 0; l < n; l++)
        if (a[l] == a[mid])
            break;
    for (r = l + 1; r < n; r++)
        if (a[r] != a[mid])
            break;
}
void asd(int *a, int n)
{
    int l, r, mid = n >> 1;
    range(a, n, l, r);
    int now = r - l; //对减,得出元素的个数

    if (now > num) //是否需要更新
        num = now, zs = a[mid];

    //如果左右两边,剩余空间,比当前【众数の个数】大,说明还有机会
    if (l > num)
        asd(a, l);//划掉与mid相等的数字,搜 L个(左区域L个)
    if (n - r > num)
        asd(a + r, n - r);//划掉mid相同数字,(右区域n-r个)
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        scanf("%d", &a[i]);
    sort(a, a + n);
    asd(a, n);
    printf("众数:%d    个数:%d\n", zs, num);
    return 0;
}

/*

48
2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6 2 1 2 4 2 6

*/
//划分思想:左侧残部(共l个)如果比当前【最大重数】大
//才有存在更大重数的可能性,才向下递归
//关于范围 1 2 2      2 4 5 距离
// L=1 R=4(实际要搜1  45两块)
// a:最左侧开始          l=1 搜1
// a+r:  4号位置开始  n-r=2搜45

递归二分

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int n, m, a[N], t;

int getL(int t, int l, int r)
{
    if (l >= r)
        return l;
    int mid = l + r >> 1;
    if (a[mid] >= t)
        getL(t, l, mid);
    else
        getL(t, mid + 1, r);
}

int getR(int t, int l, int r)
{
    if (l >= r)
        return r;
    int mid = l + r + 1 >> 1;
    if (a[mid] <= t)
        getR(t, mid, r);
    else
        getR(t, l, mid - 1);
}

int main()
{

    scanf("%d%d", &n, &m);
    for (int x = 0; x < n; x++)
        scanf("%d", &a[x]);
    while (m--)
    {
        scanf("%d", &t);
        int l = getL(t, 0, n - 1);
        if (a[l] != t)
        {
            printf("-1 -1\n");
            continue;
        }
        int r = getR(t, 0, n - 1);
        printf("%d %d\n", l, r);
    }
    return 0;
}

非递归二分

#include <iostream>
#include <stdio.h>
#include <algorithm>
using namespace std;
const int N = 1e5 + 7;
int n, m, a[N];

//《模板示例》
//更新边界R在左侧,L在右侧
//[l,mid]-->r=mid (左侧)    l=mid+1
//[mid,r]-->l=mid (右侧)    r=mid-1

/*
二分搜索,应用于具有单调性的序列之中
    现有一串不减序列,给到n个数字和m个询问
数组内编号0~n-1
    输出所询问数字的启止范围
    如果不存在输出-1 -1
*/
bool check(int a)
{ //满足性质的mid处在【左右】哪个区间
    return true;
}
int T1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1; //无需+1型,mid在R位置
        if (check(a[mid]))    //此时满足的mid在右边区间,答案在左区间,更新R(左区间的右端点)
            r = mid;
        else
            l = mid + 1;
    }
    return l;
}
int T2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1; //无需+1型,mid在R位置
        if (check(a[mid]))        //此时满足的mid在左边区间,答案在右区间,更新L(右区间的左端点)
            l = mid;
        else
            r = mid - 1;
    }
    return l;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int x = 0; x < n; x++)
        scanf("%d", &a[x]);
    int t;
    while (m--)
    {
        scanf("%d", &t);
        int l = 0, r = n - 1;
        while (l < r) //寻找左侧出发点
        {
            int mid = l + r >> 1;
            if (a[mid] >= t)
                r = mid;
            else
                l = mid + 1;
        }
        if (a[l] != t) //首次搜索都搜不到说明根本没有
        {
            printf("-1 -1\n");
            continue;
        }

        printf("%d ", l); //否则,得出起始位置

        l = 0, r = n - 1; //寻找右侧终止点
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (a[mid] <= t)
                l = mid;
            else
                r = mid - 1;
        }
        printf("%d\n", r);
    }

    return 0;
}

/*
思想过程:【追寻左端点】
    1.宏观lr步数,查询t输入
    2.while,lr没有交叠
        A.mid=l+r>>1(暂定)
思想论:从不满足性质的一侧开始找,部署【首个满足条件元素具有的性质】(左侧开始找,递增,找首个>=)
    应该说左侧都是小的数字,右侧都是大的数字
        B.关于 a[mid]的判断
            a.首个满足性质的部署mid
思想论:mid满足性质,说明落在了右区间,我们要寻找的左端点应该在左区间找
    向下取整时:左区间[l,mid]    右区间[mid+1,r]
    向上取整时:左区间[l,mid-1]  右区间[mid,r]
    跳出循环时候交叠,r在左侧,l在右侧
            b.思考1:r=mid(mid在右区间,我们要去左区间找,所以更新【左区间的右端点r】)
            c.反推1:l=mid+1
            d.反推2:l+r>>1
思想论:mid踩在左侧r说明向下取整不用+1,mid踩在右侧l说明向上取整需要+1

结束的时候看一下a[l]不相等说明这玩意压根就不存在
    我们的追踪方法会让l停留在【首个满足该性质的元素】位置上


思想过程:【追寻右端点】
    1.宏观lr步数,查询t输入
    2.while,lr没有交叠
        A.mid=l+r>>1(暂定)
思想论:从不满足性质的一侧开始找,部署【首个满足条件元素具有的性质】(左侧开始找,递增,找首个>=)
    应该说左侧都是小的数字,右侧都是大的数字
        B.关于 a[mid]的判断
            a.首个满足性质的部署mid
思想论:mid满足性质,说明落在了左区间,我们要寻找的右端点应该在右区间找
    向下取整时:左区间[l,mid]    右区间[mid+1,r]
    向上取整时:左区间[l,mid-1]  右区间[mid,r]
    跳出循环时候交叠,r在左侧,l在右侧
            b.思考1:l=mid(mid在左区间,我们要去右区间找,所以更新【右区间的左端点l】)
            c.反推1:r=mid-1
            d.反推2:l+r+1>>1
思想论:mid踩在右侧l说明向上取整,需要+1



*/

被遗弃的章节

 1.简单的阶乘

#include <iostream>
#include <stdio.h>
using namespace std;
#define ull unsigned long long
ull JC(ull k)
{
    if (k == 1) //终止条件
        return 1;
    return JC(k - 1) * k;
}

int main()
{
    // n元素阶乘测试
    ull n;
    printf("nの阶乘,请输入n:");
    scanf("%llu", &n);
    printf("%llu", JC(n));
    return 0;
}

2.简单的斐波那契数列

#include <stdio.h>
#include <iostream>
#include <math.h>
using namespace std;
#define ull unsigned long long

ull fib(ull k)
{
    if (k <= 1)
        return 1;
    return fib(k - 1) + fib(k - 2);
}

int main()
{
    //斐波那契测试
    ull n;
    printf("斐波那契测试,输入n:");
    scanf("%llu", &n);
    printf("%llu", fib(n));

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

影月丶暮风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值