栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

蓝桥杯算法竞赛培训(二) 汉诺塔与STL

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

蓝桥杯算法竞赛培训(二) 汉诺塔与STL

文章目录
    • 1.递归-汉诺塔
      • 问题描述
      • 初步分析
      • 代码
      • 复杂度分析
      • 进阶技巧
        • 1. n = 1000 n=1000 n=1000时的移动次数:取模
          • 1.对加减乘除操作的取模
          • 2.大数取模
          • 3.阶乘取模
        • 2. n = 1000000000000 n=1000000000000 n=1000000000000时的移动次数:快速幂
        • 3.问题变形
          • 分析
    • 2.STL
      • 不定长数组vector
        • 字符串
        • 队列
        • 非线性结构:红黑树(set,map)
          • 1.set
        • 预习

1.递归-汉诺塔

问题描述

  我们的目的是要将整个塔移动到另一根桩柱上,每次只能移动一个圆盘,且较大的圆盘在移动过程中不能放置在较小的圆盘上面.问你具体的移动方案.

初步分析

   用递归的思想分析我们知道,对于最下面的盘子,如果上面的 n − 1 n-1 n−1个盘子能够全部移动到柱 C C C上,我们就只需要将第n个盘子放到 B B B柱上,之后对在 C C C柱子上的 n − 1 n-1 n−1个盘子做相同的操作将其移动到 B B B盘上,整个过程就完成了。

代码
void Move (int n , char a , char b , char c){
    if (n == 1) {
        cout << "第" << n << "号盘:" << a << "->" << b << endl;
        return ;
    }
    Move(n - 1 , a , c , b);
    cout << "第" << n << "号盘:" << a << "->" << b << endl;
    Move(n - 1 , c , b , a);
}
int main()
{
    int n;
    while (cin >> n){
        Move(n , 'A' , 'B' , 'C');
    }
    return 0;
}
复杂度分析

1.直接看输出行数,不难得出规律: T ( n ) = 2 n − 1 T(n)=2^n-1 T(n)=2n−1,则复杂度为 O ( 2 n ) O(2^n) O(2n).结合归纳法可证明

3.令 T n T_n Tn​为 n n n个盘子的移动次数,那么 T n = 2 ∗ T n − 1 + 1 T_n=2*T_{n-1}+1 Tn​=2∗Tn−1​+1,边界: T 1 = 1 T_1=1 T1​=1

令 U n = T n + 1 U_n=T_n+1 Un​=Tn​+1

则 U n = 2 ∗ T n − 1 + 1 + 1 = 2 ( T n − 1 + 1 ) = 2 U n U_n=2*T_{n-1}+1+1=2(T_{n-1}+1)=2U_n Un​=2∗Tn−1​+1+1=2(Tn−1​+1)=2Un​,边界: U 1 = T 1 + 1 = 2 U_1=T_1+1=2 U1​=T1​+1=2

所以 U n = 2 n U_n=2^n Un​=2n,所以 T n = U n − 1 = 2 n − 1 T_n=U_n-1=2^n-1 Tn​=Un​−1=2n−1

进阶技巧 1. n = 1000 n=1000 n=1000时的移动次数:取模

  由于该递推式增加的太快了,如果要求具体解很容易爆long long,所以一般题目会要求取模.所以这里顺便给大家展开讲一下取模的技巧

1.对加减乘除操作的取模
// 减法
int sub (int a , int b , int mod){
    return ((a - b) % mod + mod) % mod;
}
int mul (int a , int b , int mod){
    return ((a * b) % mod + mod) % mod;
}
// 除法 不能直接取模,需要求逆元.
int div (int a , int b , int mod){
    // 错误做法
    return ((a / b) % mod + mod) % mod;
}
2.大数取模
#include 
using namespace std;
#define ll long long
const int maxn = 3e5 +5;
const int mod = 1e9 + 7;
int main()
{
    string a;
    cin >> a;
    int b = 0;
    for (auto x : a){
        int dig = x - '0';
        b = (b * 10 + dig) % mod;
    }
    // b = a;
    cout << b << endl;
    
    return 0;
}

3.阶乘取模

阶乘取模,且模数比较小

x !   %   m x! % m x! % m

x ∈ [ 1 , 1 0 1000000 ] , m = 6 x in[1,10^{1000000}],m=6 x∈[1,101000000],m=6

结论: 当 x > = m x >= m x>=m时,全部等于0.

#include 
using namespace std;
#define ll long long
const int maxn = 3e5 +5;
const int mod = 1e9 + 7;
int main()
{
    string a;
    cin >> a;
    int m = 666;
    if (a.size() > 3) {
        cout << 0 << endl;
        return 0;
    }
    stringstream b;
    b << a;
    int c;
    b >> c;
    if (c >= 666){
        cout << 0 << endl;
        return 0;
    }
    int ans = 1;
    for (int i = 1 ; i <= c ; i++){
        ans = (ans * i) % m;
    }
    cout << ans << endl;
    return 0;
}

2. n = 1000000000000 n=1000000000000 n=1000000000000时的移动次数:快速幂

目标:求解 2 n 2^n 2n
思路:
  1.先考虑求解一个这样的序列: 2 1 , 2 2 , 2 4 , . . . 2^1,2^2,2^4,... 21,22,24,...,即数列: x i = 2 2 i − 1 x_i=2^{2^{i-1}} xi​=22i−1

   2.我们发现 x i = 2 2 ∗ 2 i − 2 = 2 2 i − 2 ∗ 2 2 i − 2 = x i − 1 ∗ x i − 1 x_i=2^{2*2^{i-2}}=2^{2^{i-2}}*2^{2^{i-2}}=x_{i-1}*x_{i-1} xi​=22∗2i−2=22i−2∗22i−2=xi−1​∗xi−1​

   3.对于 n n n,将其二进制分解得到: n = a 0 2 0 + a 1 2 1 + . . . + a k 2 k ,     a i ∈ [ 0 , 1 ] n=a_02^{0}+a_12^{1}+...+a_k2^{k}, a_i in [0,1] n=a0​20+a1​21+...+ak​2k,   ai​∈[0,1]

则有: 2 n = 2 a 0 ∗ 2 0 + a 1 2 1 + . . . + a k 2 k = 2 a 0 ∗ 2 0 ∗ 2 a 1 ∗ 2 1 ∗ . . . ∗ 2 a k ∗ 2 k 2^{n}=2^{a_0*2^{0}+a_12^{1}+...+a_k2^{k}}=2^{a_0*2^{0}}*2^{a_1*2^{1}}*...*2^{a_k*2^{k}} 2n=2a0​∗20+a1​21+...+ak​2k=2a0​∗20∗2a1​∗21∗...∗2ak​∗2k

对于等式左边,直接求是 O ( n ) O(n) O(n)的,但是对于等式右边,我们发现它只有 O ( l o g n ) O(logn) O(logn)项的,而且这些项可以用第二个步骤的 x i = x i − 1 ∗ x i − 1 x_i=x_{i-1}*x_{i-1} xi​=xi−1​∗xi−1​来进行转移。

所以我们维护两个东西: A n s = 1 Ans=1 Ans=1代表最终答案, x x x代表第二步中的序列.从低位到高位遍历 n n n的二进制位,当 a i = 1 a_i =1 ai​=1,则 A n s ∗ = x Ans *= x Ans∗=x.否则就不乘.然后每一步转移 x = x ∗ x x=x*x x=x∗x,这就是快速幂的过程,代码如下所示.

// 求a^b % mod
int ksm (int a , int b , int mod){
    int ans = 1 , x = a;
    while (b){
        if (b & 1) ans = ans * x % mod;
        b >>= 1; // 除二
        x *= x;
    }
}
3.问题变形

  把有 n 个圆盘的塔从左边的桩柱A移动到右边的桩柱B,不允许在A和B之间直接移动,求最短的移动序列.(每一次移动都必须是移动到中间的桩柱或者从中间的桩柱移出.像通常一样,较大的圆盘永远不能放在较小圆盘的上面.)

分析

T n = T n − 1 + 1 + T n − 1 + 1 + T n − 1 = 3 T n − 1 + 2 T_n=T_{n-1}+1+T_{n-1}+1+T_{n-1}=3T_{n-1}+2 Tn​=Tn−1​+1+Tn−1​+1+Tn−1​=3Tn−1​+2
变形:
求解 T n = 3 T n − 1 + 1 T_n=3T_{n-1}+1 Tn​=3Tn−1​+1, T 1 = 1 T_1=1 T1​=1
推广:
求解 T n = a T n − 1 + b T_n=aT_{n-1}+b Tn​=aTn−1​+b

证明为何 a k − 1 a − 1 frac{a^k-1}{a-1} a−1ak−1​为何总是整数?

2.STL

不定长数组vector
    #include // 涵盖所有头文件,蓝桥杯允许使用
    using namespace std;
    int a[100]; // 定义了一个长度为100的数组 - 定长的
    vector b; // 定义了一个长度为0的数组 - 变长的
    int main()
    {
        // vector 有哪些操作呢?
        // 1.获得大小
        cout << b.size() << endl;
        // 在数组末尾添加一个元素
        b.push_back(100);
        // 删去数组末尾一个元素
        b.pop_back();
        // 2.访问,注意下标从0开始的
        cout << b[0] << endl;
        // 访问所有
    
        // 第一种方法:
        for (int i = 0 ; i < 10 ; i++){
            // b[i]
            b.push_back(i);
        }
        // 第二种方法 C++11
        // 分号右边:是我们要循环遍历的《集合》
        // 分号左边:auto 代表我们自动获取右边集合中每个元素的类型
        // 实际运行时会把auto替换成目标类型
        // g 就是将集合中每个数取出来时所临时储存的变量
        // 什么时候用:只需要遍历每个数,而不需要知道每个数的位置时.
        for (auto g : b){
            cout << g << " ";
        }
        cout << endl;
        return 0;
    }

测试:
1.用vector重写约瑟夫环。

字符串

定义:每个元素为字符的数组.

    #include // 涵盖所有头文件,蓝桥杯允许使用
    using namespace std;
    int main()
    {
        string a , b;
        cin >> a >> b;
        // 1.字符串拼接
        cout << a + b << endl;
        // 2.字符串大小比较-介绍
        // 3.反转字符串
        // 4.对字符串进行排序
        sort(a.begin() , a.end());
        // 5.对字符串进行翻转
        reverse(a.begin() , a.end());
        // 6.寻找一个子串
    	// 7.访问字符串: back []
    
        return 0;
    }

测试:
回文串判断

队列
    #include // 涵盖所有头文件,蓝桥杯允许使用
    using namespace std;
    queue q;
    int main()
    {
        // queue 有哪些操作呢?
        // 1.获得大小
        cout << q.size() << endl;
        // 2.在队尾添加一个元素
        q.push(10);
        // 3.访问,只能从队头(front)访问
        // 访问之前应该要先检查队列是否是空.
        cout << q.front() << endl;
        // 4.出队
        q.pop();
        return 0;
    }
#include // 涵盖所有头文件,蓝桥杯允许使用
using namespace std;
stack s;
int main()
{
    // queue 有哪些操作呢?
    // 1.获得大小
    cout << s.size() << endl;
    // 2.访问,只能访问栈顶且访问之前应该要先检查栈是否是空.
    if (s.size() != 0)
        cout << s.top() << endl;
    // 3.在栈中添加一个元素
    s.push(10);
    // 4.弹出栈顶元素
    s.pop();
    return 0;
}

测试:
1.括号匹配
https://www.luogu.com.cn/problem/P1739 ,求和

非线性结构:红黑树(set,map)

主要考虑如何使用它

1.set

一个自带<去重机制>的有序集合.

   #include // 涵盖所有头文件,蓝桥杯允许使用
    using namespace std;
    set s;
    int main()
    {
        // 1.插入元素 复杂度:O(logn)
        s.insert(5);
        // 2.获取集合大小 O(1)
        cout << s.size() << endl;
        // 3.删除某个元素 O(logn)
        s.erase(5);
        // 4.查询集合最小值 O(1)
        cout << *s.begin() << endl;
        // 5.查询集合最大值 O(1)
        cout << *s.rbegin() << endl;
        // 6.查询某个值是否存在
        if (s.count(5)) //TODO:
        else
        // 遍历整个集合
        for (auto g : s){
            //
        }
        return 0;
    }
    
预习

multiset:
  一个允许重复值出现的有序集合.

map:
  它的本质就是个数组.只是下标不一定是整数.
map应用:

  1.给你一个长度为n的正整数。然后 Q Q Q次询问,每次询问你整数 x ( x ≤ 1 e 9 ) x(x leq 1e9) x(x≤1e9)出现了多少次.

  2.给你n个字符串。然后 Q Q Q次询问,每次询问你字符串 s s s出现了多少次.

  3.给你一个 n ∗ m ( n , m ≤ 1 e 5 ) n*m(n,m leq 1e5) n∗m(n,m≤1e5)的矩阵。 Q ( ≤ 1 e 5 ) Q(leq 1e5) Q(≤1e5)次操作.每次操作在位置 ( x , y ) (x,y) (x,y)放置一个苹果.最后问你多少个格子有格子.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/868985.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号