蓝桥杯第一场算法双周赛(1~3题)

342 阅读5分钟

本人能力有限,只做出前三题。有时间会看一下后面三题。 前两题很简单,直接上代码,第三题用到了二分法(有详解)。

1. 三带一【算法赛】

问题描述

小蓝和小桥玩斗地主,小蓝只剩四张牌了,他想知道是否是“三带一”牌型。所谓“三带一”牌型,即四张手牌中,有三张牌一样,另外一张不与其他牌相同,换种说法,四张手牌经过重新排列后,可以组成 AAABAAAB 型。

输入格式

第一行输入一个整数 TT ,代表斗地主的轮数。接下来 TT 行,每行输入一个长度为 44 的字符串,代表小蓝的手牌。字符 { 'A','2','3','4','5','6','7','8','9','X','J','Q','K' } 对应代表牌面 { A,2,3,4,5,6,7,8,9,10,J,Q,KA, 2, 3, 4, 5, 6, 7, 8, 9,10, J, Q, K } 。牌面中不包含大小王。

输出格式

输出 TT 行,每行一个字符串,如果当前牌是“三带一”牌型,输出 Yes ,否则输出 No

样例输入

5
AAAA
33X3
JQKX
6566
KKKQ

样例输出

No
Yes
No
Yes
Yes

说明“四炸”牌型不属于“三带一”牌型。

评测数据范围

数据范围:1T501 \le T \le 50 。字符中只包含:{ A,2,3,4,5,6,7,8,9,X,J,Q,KA, 2, 3, 4, 5, 6, 7, 8, 9, X, J, Q, K } 。

AC代码

只需要判断是否存在三个相同字符即可。

signed main()
{
    int t;
    cin >> t;
    while (t--)
    {
        map<char, int> m;
        string s;
        cin >> s;
        for (int i = 0; i < s.size(); i++)
        {
            m[s[i]]++;
        }
        bool f = false;
        for (auto i : m)
        {
            if (i.second == 3)
            {
                cout << "Yes" << endl;
                f = true;
                break;
            }
        }
        if (!f) cout << "No" << endl;
    }
    return 0;
}

2. 数树数【算法赛】

问题描述

小蓝最近学了二叉树,他想到了一个问题。 给定一个层数为 nn 的满二叉树,每个点编号规则如下:具体来说,二叉树从上向下数第 pp 层,从左往右编号分别为: 1,2,3,4...2p11,2,3,4...2^{p-1} 。\n\n小蓝给你一条从根节点开始的路径,他想知道到达的节点编号是多少。例如:路径是 rightleftright-left ,那么到达的节点是 1231-2-3 ,最后到了三号点。

输入格式

第一行输入两个整数 n,qn,qnn 表示完全二叉树的层数,qq 代表询问的路径数量。接下来 qq 行,每行一个字符串 SSSS 只包含字符 { 'L','R' },'L' 代表向左,R 代表向右。

输出格式

输出 qq 行,每行输出一个整数,代表最后到达的节点编号。

样例输入

3 6 R L LL LR RL RR

样例输出

2 1 1 2 3 4

说明

2n20,1q103,1S<n2 \le n \le 20, 1 \le q \le 10^3, 1 \le |S| \lt n完全二叉树:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为 KK ,且节点总数是 2k12^k-1 ,则它就是满二叉树。

思路

简单提示一下,多花几层二叉树,观察一下就可以发现,走R的话是当前结点编号* 2,走L的话,是2* 当前结点编号-1。

AC代码

signed main()
{
    int n, t;
    cin >> n >> t;
    while (t--)
    {
        int k = 1;
        string s;
        cin >> s;
        for (int i = 0; i < s.size(); i++)
        {
            if (s[i] == 'R') k *= 2;
            else k = 2*k - 1;
        }
        cout << k << endl;
    }
    return 0;
}

3. 分组【算法赛】

问题描述

蓝桥小学要进行弹弹球游戏,二年级一班总共有 nn 个同学,要求分成 kk 个队伍,由于弹弹球游戏要求队员的身高差不能太大,小蓝是班长,他对这个事情正在发愁,他想问你,如何最小化每个组之间的身高极差。具体的,假设分成了 kk 个组,第 ii 组最高的人身高是 HxiHx_i ,最矮的是 HniHn_i,你被要求最小化表达式: max1ik(HxiHni)\underset{1 \leq i \leq k}{\max} (Hx_i-Hn_i) 。直白来说,你需要将 nn 个元素分出 kk 组,使得最大的极差尽可能小。你需要输出这个最小化后的值。

输入格式

第一行输入两个整数 n,kn, k 。 第二行输入 nn 个整数:h1,h2,h3...hnh_1, h_2,h_3...h_n ,分别代表 nn 个人的身高。

输出格式

输出一个整数,代表最小值。

样例输入

5 3
8 4 3 6 9

样例输出

1

说明

样例分组情况:{ 3,43,4 } ,{ 66 } ,{ 8,98,9 } 。

评测数据规模

数据范围: 1kn105,1hi1091\le k \le n \le 10^5, 1 \le h_i \le10^9

解题思路

读了一遍题,没什么思路,再读一遍,观察数据,可以发现最小的极差和最大的极差都是可以确定的,最小的就是0,很好理解,最大的就是h[n-1] - h[0](或者无脑一些直接1e9)。这样大的数据范围,立马就想到用二分来做。这里,我们二分的是可能的最小化极差。然后就该构造二分答案的check()函数了,在二分的过程中,我们必须保证分成的组数为k,那如何实现呢?
只需将排序后的数组进行遍历,记录当前组的最矮身高,如果当前成员身高在该组中与最矮成员身高差大于mid,则将其重新分一组。最后看组数是否小于等于k
为什么要小于等于k呢?而不是大于等于?
极差相同的情况下,分的组越少,说明情况越乐观,如过满足cnt<=k,说明mid符合条件,我们还能进一步缩小右侧范围,看看比mid小的部分能否满足条件(我们的目标就是要找到max极差的最小化的值),反之,缩小左侧范围即可。

AC代码

#include<iostream>
#include<algorithm>
using namespace std;
#define int long long
#define endl '\n'
typedef long long ll;

const int N = 100010;
int h[N];
int n, k;
bool check(int m)
{
    int cnt = 1;//组数
    int minh = h[0];//当前组最小的身高
    for (int i = 1; i < n; i++)
    {
        if (h[i]-minh > m)//当前成员身高在该组中与最矮成员身高差过大,将其新分一组
        {
            cnt++;
            minh = h[i];
        }
    }
    if (cnt <= k) return true; //组数越少,极差越小
    else return false;
    //return cnt<= k;
}

signed main()
{
    
    cin >> n >> k;
    for (int i = 0; i < n; i++)
    {
        cin >> h[i];
    }
    sort(h, h + n);
    int l = 0, r = 1e9;
    while (l < r)
    {
        int mid = (l + r) / 2;
        if (check(mid))
        {
            r = mid;
        }
        else
        {
            l = mid + 1;
        }
    }
    cout << l;
    
    return 0;
}