【冲刺蓝桥杯】牛客竞赛补题 + 算法模板总结

18 篇文章 6 订阅
13 篇文章 0 订阅

🍎 博客主页:🌙@披星戴月的贾维斯
🍎 欢迎关注:👍点赞🍃收藏🔥留言
🍇系列专栏:🌙 C/C++专栏
🌙请不要相信胜利就像山坡上的蒲公英一样唾手可得,但是请相信,世界上总有一些美好值得我们全力以赴,哪怕粉身碎骨!🌙
🍉一起加油,去追寻、去成为更好的自己!

在这里插入图片描述

提示:以下是本篇文章正文内容,下面案例可供参考


前言

    蓝桥杯在悄无声息中就来了,我上次参加就仅仅只拿了一个省三,期待通过自己的努力,能够更进一步,也希望在这和大家分享的竞赛题和模板你们能用得上。一起加油!

🍎1、A-画牌河

🔥1.1题目链接 画牌河
来源:牛客小白月赛67A题

🔥1.2题目描述
x魂是一款深受acmer喜爱的立直麻将游戏,在x魂中,玩家打的牌会放入牌河中,牌河是一个33行66列的区域,牌先放到第一行,放满后再在下一行放,同一行的牌是从左往右依次放的。

输入一个x,请你画出放置了x张牌的牌河。

输入描述

输入一个整数 x(0≤x≤18),表示放置的牌数。

输出描述:

输出一个3行6列的区域,每个位置为1表示放置了一张牌,为0表示为空。

示例1:

输入

8

输出

111111
110000
000000

🔥1.3分析题意
    A题,简单模拟即可,就是把矩阵n位置成1,只要一个for循环暴力。

🔥1.4C++代码示例

#include<bits/stdc++.h>
using namespace std;
int a[4][20];
int n;
int main ()
{
    cin >> n;
    for(int i = 0; i < 3; i++)
    {
        for(int j = 0; j < 6; j++)
        {
            if(n > 0)
            {
                a[i][j] = 1;
                n --;
            }
        }
    }
    for(int i = 0; i < 3; i ++)
    {
        for(int j = 0; j < 6; j++)
        {
            cout << a[i][j];
        }
        cout << endl;
    }
    return 0;
}

🍎2、不点两面(easy version)

🔥2.1题目链接🔥不点两面(easy version)

🔥2.2题目描述🔥
x魂是一款深受acmer喜爱的立直麻将游戏,在x魂中,认为不点两面听牌的牌是比较安全的,本题需要你求出在只有万子的x魂中,有几种牌是比较安全的。

具体来说,每张牌上有一个数字,数字范围在1到m之间。场上还有一个对手的牌河,对手的牌河中有若干张对手已经打出的牌。定义比较安全牌为:该牌上写有的数字x满足x−3或x+3至少在对手牌河里出现过一次。

请你求出,在m种牌中有几种牌是比较安全的。

对手的牌河初始为空,对手会不断向牌河中加入或移出一张牌共q次,你需要在每次对手操作后都给出当前状况下的答案。

输入描述:

输入第一行是两个整数m,q(1≤m≤100,1≤q≤200),含义如题目所述。
接下来qq行,每行输入两个整数op,num(1≤op≤2,1≤num≤m),表示一次操作,具体来说:
op=1表示对手向牌河中加入了一张num;
op=2表示对手从牌河中移出了一张num,保证移出的牌操作前一定在牌河里有至少一张。
注意牌河中可能有写有相同数字的牌出现多次。

输出描述

输出q行,第i行表示第i次操作后,m种牌中有几种牌是比较安全的。

示例1:

输入:

10 5
1 4
1 10
1 4
2 4
2 4

输出

2
2
2
2
1

🔥2.3分析题意
    根据题意,我们要在1 - m中找到符合 i + 3 或者 i - 3 == x, x是牌河中有的数, 而且牌河中的数可能会重复, 因此我们要想一种数据类型既可以方便我们插入删除,又能查找比较方便,而且数据大小还是可以接受的q <= 200,针对查找vector数组中是否有num这个元素,可以用count接口来实现.

🔥2.4C++代码示例

#include<bits/stdc++.h>
using namespace std;
int m, q;
vector<int> ph;
bool st[200];
int main ()
{
    cin >> m >> q;
    while(q --)
    {
        memset(st, 0, sizeof st);//每次我们重置st 数组
        int op, num;
        int res = 0;
        cin >> op >> num;
        if(op == 1)
        {
            ph.push_back(num);
        }
        else
        {
            auto it = ph.begin();
            while(it != ph.end())
            {
                if(*it == num) break;
                else ++it;
            }
            ph.erase(it);
        }
        for(int i = 1; i <= m; i++)
        {
            int h = i + 3, f = i - 3;
            if(count(ph.begin(), ph.end(), h) && st[i] == false)
            {
                res++;
                st[i] = true;
            }
            if(count(ph.begin(), ph.end(), f) && st[i] == false)
            {
                res++;
                st[i] = true;
            }
            
        }
        cout << res << endl;
    }
    
    return 0;
}

我看了别人的题解发现了一种更好的做法,直接用数组操作,简单,速度又快
链接
题意:在1到m的数字中满足 x-3或x+3 在 牌河中出现的数字个数

题解:定义一个数组a[N],来统计数字x出现的次数。定义sum表示不重复数字的个数。

每往牌河中添加一个数字,a[x-3]++ , a[x+3]++。如果a[x-3]==0,那么sum++;a[x+3]同理。

每在牌河中删除一个数组,a[x-3]-- , a[x+3]–。如果a[x-3]==0,那么sum–;a[x+3]同理。
代码示例:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <math.h>
#include <string>
#include <set>
#define PI 3.14159
using namespace std;

typedef pair<int,int> PII;
typedef long long LL;
const int MAX_INT =  0x3f3f3f3f;
const int N = 1e5+15;
const int mod = 1e9+7;
int a[N];

void solve()
{
    int m,q;
    cin>>m>>q;
    int sum = 0;
    while(q--)
    {
        int op,num;
        cin>>op>>num;
        if(op==1)
        {
            int x = num - 3;
            int y = num + 3;
            if(x>=1)
            {
                if(a[x]==0)sum++;
                a[x]++;
            }
            if(y<=m)
            {
                if(a[y]==0)sum++;
                a[y]++;
            }
        }
        else
        {
            int x = num - 3;
            int y = num + 3;
            if(x>=1)
            {
                a[x]--;
                if(a[x]==0)sum--;
            }
            if(y<=m)
            {
                a[y]--;
                if(a[y]==0)sum--;
            }
        }
        cout<<sum<<endl;
    }
    
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    
    int T = 1;
    while(T--)
    {
        solve();
    }
}

🍎3、开题顺序

🔥3.1题目链接 开题顺序

由于这一题用到的算法是暴力求全排列+枚举,我们可以先去看acwing 递归求全排列的模板题,看完这题,做开题顺序就比较简单,而且容易理解。
递归实现排列型枚举

#include<bits/stdc++.h>
using namespace std;
const int N = 12;
int a[N];
bool st[N]; //判断这个值是不是被用过
int n;
void dfs(int u)
{
    if(u >= n)
    {
        for(int i = 0; i < u; i++)
        {
            cout << a[i] << " ";
        }
        cout << endl;
        return;
    }
    for(int i = 1; i <= n; i++)
    {
        if(!st[i])
        {
            st[i] = true;
            a[u] = i;
            dfs(u + 1);
            st[i] = false;//恢复现场
        }
    }
}
int main ()
{
    cin >> n;
    dfs(0);//从第0个数和0状态开始搜索
    return 0;
}

**题目描述 **
在这里插入图片描述在这里插入图片描述
🔥3.3分析题意
    看数据范围1 <= n <= 9,我们就知道这题可以用dfs过,这道题的分数大小会和做题有关,所以我们要枚举所有的开题可能,即递归枚举全排列,代码如下。

🔥3.4代码示例

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n, t, p;
const int N = 12;
int tmp[N];
bool st[N];
LL ans;
LL a[N], b[N], c[N], x[N], y[N];
//枚举全排列
void dfs(int u, int T)
{
    if(u >= n || T >= t )
    {
        LL res =0, ti = 0;
        for(int i = 0; i < u; i++)
        {
            auto &idx = tmp[i];
            ti += x[idx];
            if(ti <= t) res += max(c[idx], a[idx] - ti * b[idx] - y[idx] * p);
			else break;
		}
		ans = max(ans, res);
		return;
    }
    for(int i = 1; i <= n; i++)
	{
		if(!st[i])
		{
			st[i] = true;
			tmp[u] = i;
			dfs(u + 1, T + x[i]);
			st[i] = false;
		}
	}
}
    
int main()
{
    cin >> n >> t >> p;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i] >> c[i] >> x[i] >> y[i];    
    }
    dfs(0, 0);
    cout << ans << endl;
    return 0;
}

🍎4、算法模板总结- 来源:acwing

整数二分算法模板

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

浮点数二分算法模板

bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

一维前缀和

S[i] = a[1] + a[2] + ... a[i]
a[l] + ... + a[r] = S[r] - S[l - 1]

二维前缀和

S[i, j] = 第i行j列格子左上部分所有元素的和
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1 - 1, y2] - S[x2, y1 - 1] + S[x1 - 1, y1 - 1]

一维差分

给区间[l, r]中的每个数加上c:B[l] += c, B[r + 1] -= c

二维差分

void add(int x1, int y1, int x2, int y2, int c)
{
    b[x1][y1] += c;
    b[x1][y2 + 1] -= c;
    b[x2 + 1][y1] -= c;
    b[x2 + 1][y2 + 1] += c;
}

a[i][j] = a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1] + b[i][j];//这里使用二维前缀和公式

双指针算法

for (int i = 0, j = 0; i < n; i ++ )
{
    while (j < i && check(i, j)) j ++ ;

    // 具体问题的逻辑
}
常见问题分类:
    (1) 对于一个序列,用两个指针维护一段区间
    (2) 对于两个序列,维护某种次序,比如归并排序中合并两个有序序列的操作

单链表

// head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;

// 初始化
void init()
{
    head = -1;
    idx = 0;
}

// 在链表头插入一个数a
void insert(int a)
{
    e[idx] = a, ne[idx] = head, head = idx ++ ;
}

// 将头结点删除,需要保证头结点存在
void remove()
{
    head = ne[head];
}

模拟栈

// tt表示栈顶
int stk[N], tt = 0;

// 向栈顶插入一个数
stk[ ++ tt] = x;

// 从栈顶弹出一个数
tt -- ;

// 栈顶的值
stk[tt];

// 判断栈是否为空,如果 tt > 0,则表示不为空
if (tt > 0)
{

}

模拟队列

// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;

// 向队尾插入一个数
q[ ++ tt] = x;

// 从队头弹出一个数
hh ++ ;

// 队头的值
q[hh];

// 判断队列是否为空,如果 hh <= tt,则表示不为空
if (hh <= tt)
{

}

单调栈

常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
    while (tt && check(stk[tt], i)) tt -- ;
    stk[ ++ tt] = i; //元素入队列
}

单调队列 -常用于解决滑动窗口问题

常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
    while (hh <= tt && check_out(q[hh])) hh ++ ;  // 判断队头是否滑出窗口
    while (hh <= tt && check(q[tt], i)) tt -- ;
    q[ ++ tt] = i;
}

并查集

(1)朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);


(2)维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);


(3)维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

C++ STL简介

vector, 变长数组,倍增的思想
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比较运算,按字典序

pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

string,字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,(子串长度))  返回子串
    c_str()  返回字符串所在字符数组的起始地址

queue, 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front()  返回队头元素
    back()  返回队尾元素
    pop()  弹出队头元素

priority_queue, 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

stack, 栈
    size()
    empty()
    push()  向栈顶插入一个元素
    top()  返回栈顶元素
    pop()  弹出栈顶元素

deque, 双端队列
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []

set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驱和后继,时间复杂度 O(logn)

    set/multiset
        insert()  插入一个数
        find()  查找一个数
        count()  返回某一个数的个数
        erase()
            (1) 输入是一个数x,删除所有x   O(k + logn)
            (2) 输入一个迭代器,删除这个迭代器
        lower_bound()/upper_bound()
            lower_bound(x)  返回大于等于x的最小的数的迭代器
            upper_bound(x)  返回大于x的最小的数的迭代器
    map/multimap
        insert()  插入的数是一个pair
        erase()  输入的参数是pair或者迭代器
        find()
        []  注意multimap不支持此操作。 时间复杂度是 O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
    和上面类似,增删改查的时间复杂度是 O(1)
    不支持 lower_bound()/upper_bound(), 迭代器的++,--

bitset, 圧位
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []

    count()  返回有多少个1

    any()  判断是否至少有一个1
    none()  判断是否全为0

    set()  把所有位置成1
    set(k, v)  将第k位变成v
    reset()  把所有位变成0
    flip()  等价于~
    flip(k) 把第k位取反

🍎5、总结

    本文和大家分享了几道牛客小白月赛的题解,以及一些实用的算法模板,希望对大家的蓝桥杯备战有所帮助,最后祝大家都能取得满意的成绩!

  • 74
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 66
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 66
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

披星戴月的贾维斯

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

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

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

打赏作者

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

抵扣说明:

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

余额充值