[蓝桥杯2021初赛]

本文通过一系列编程题目展示了如何运用计算机科学的基本概念,如最短路径算法Dijkstra、动态规划、二分查找和哈希表解决实际问题。题目涵盖了从数字卡片拼接、直线计算、货物堆放方案、最短路径计算到括号序列优化和时间显示等多个方面,展示了算法在处理复杂问题时的有效性。
摘要由CSDN通过智能技术生成
  1. 卡片

题面:

小蓝有很多数字卡片,每张卡片上都是数字0 到9。

小蓝准备用这些卡片来拼一些数,他想从1 开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。

小蓝想知道自己能从1 拼到多少。

例如,当小蓝有30 张卡片,其中0 到9 各3 张,则小蓝可以拼出1 到10,但是拼11 时卡片1 已经只有一张了,不够拼出11。

现在小蓝手里有0 到9 的卡片各2021 张,共20210 张,请问小蓝可以从1拼到多少?

提示:建议使用计算机编程解决问题。

题解:

利用数组将0·9的卡片个数存入数组中,之后每用到一个就减去一个,然后当有一个数字的个数为零的时候输出当前数字,结束程序。

int main()
{
    int a[10],t;
    for (int i = 0; i < 10; i++)
        a[i] = 2021;
    for (int i=1;; i++)
    {
        t = i;
        while (t!=0)
        {
            a[t % 10]--;
            if (!a[t%10])
            {
                cout << i << endl;
                return 0;
            }
            t /= 10;
        }
    }
    return 0;
}
  1. 直线

题面:

在平面直角坐标系中,两点可以确定一条直线。

如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。

给定平面上2 × 3 个整点{(x, y)|0 ≤ x < 2, 0 ≤ y < 3, x ∈ Z, y ∈ Z},

即横坐标是0 到1 (包含0 和1) 之间的整数、纵坐标是0 到2 (包含0 和2) 之间的整数的点。

这些点一共确定了11 条不同的直线。

给定平面上20 × 21 个整点{(x, y)|0 ≤ x < 20, 0 ≤ y < 21, x ∈ Z, y ∈ Z},

即横坐标是0 到19 (包含0 和19) 之间的整数、纵坐标是0 到20 (包含0 和20) 之间的整数的点。

请问这些点一共确定了多少条不同的直线。

题解:

运用直线的两点式与横截式来求出直线的k和b,存储起来,然后对比不同的k和b并计数。

垂直于x的直线因为没有k而单独计算。

注意精度问题:认定当两个double数的差>1e-8 时二者为不同数<=1e-8时二者为相同的数。

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
struct node {
    double k, b;
    bool operator<(const node& t) {
        if (k != t.k)return k < t.k;
        else return b < t.b;
    }
}l[1000010];
int main()
{
    int x1, y1,x2,y2,n=0,ans=1;
    for (x1 = 0; x1 < 20; x1++)
        for(y1=0;y1<21;y1++)
            for(x2=0;x2<20;x2++)
                for (y2 = 0; y2 < 21; y2++)
                {
                    if (x1 != x2)
                    {
                        double k = (double)(y1 - y2) / (x1 - x2);
                        double b =y1 - k * x1;
                        l[n++] = { k,b };
                    }
                }
    sort(l, l + n);
    for (int i = 1; i < n; i++)
    {
        if (fabs(l[i].b - l[i - 1].b > 1e-8) || fabs(l[i].k - l[i - 1].k) > 1e-8)
        ans++;
    }
    ans += 20;
    cout << ans;
    return 0;
}

3.货物摆放

题面:

小蓝有一个超大的仓库,可以摆放很多货物。

现在,小蓝有n 箱货物要摆放在仓库,每箱货物都是规则的正方体。

小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。

小蓝希望所有的货物最终摆成一个大的立方体。即在长、宽、高的方向上分别堆L、W、H 的货物,满足n = L × W × H。

给定n,请问有多少种堆放货物的方案满足要求。

例如,当n = 4 时,有以下6 种方案:1×1×4、1×2×2、1×4×1、2×1×2、2×2×1、4×1×1。

请问,当n = 2021041820210418 (注意有16 位数字)时,总共有多少种

方案?

提示:建议使用计算机编程解决问题。

题解:

将能被n整除的数存储到a中,再将能被a整除的数存到b中,把结果存到c中,保持a<=b<=c的顺序,避免重复,进行分类讨论。当三个数相等时,只有1种情况,有且仅有两个数相等时,由排列组合得

有3种情况。三个数都不相等时由排列组合得有六种情况。

循环一遍可得出答案。这段代码时间复杂度较高,直接交上去会超时,所以算出答案再printf。

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
LL a, b, c, nn, ans, n = 2021041820210418, i, j;
int main()
{
    for (i = 1; i * i <= n; i++)
    {
        if (!(n % i))
        {
            a = i;
            nn = n / i;
            for (j = 1; j * j <= nn; j++)
            {
                if (!(nn % j))
                {
                    b = j;
                    c = nn / j;
                    if (a <= b && b <= c)
                    {
                    if (a == b && b == c)
                        ans++;
                    else if ((a == b && b != c) || (a != b && b == c) || (a == c && b != c))
                        ans += 3;
                    else if (a != b && b != c && a != c)
                        ans += 6;;
                    }
                    
                }
            }
        }
    }
    cout << ans;
    return 0;
}

4.路径

题面:

小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。

小蓝的图由2021 个结点组成,依次编号1 至2021。

对于两个不同的结点a, b,如果a 和b 的差的绝对值大于21,则两个结点之间没有边相连;

如果a 和b 的差的绝对值小于等于21,则两个点之间有一条长度为a 和b 的最小公倍数的无向边相连。

例如:结点1 和结点23 之间没有边相连;结点3 和结点24 之间有一条无向边,长度为24;

结点15 和结点25 之间有一条无向边,长度为75。

请计算,结点1 和结点2021 之间的最短路径长度是多少。

提示:建议使用计算机编程解决问题。

题解:

最短路模板题。构建邻接矩阵,套Dijkstra模板

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 2022;
int dist[N];
bool st[N];

int main() {
    memset(dist, 0x3f, sizeof dist);
    memset(st, false, sizeof st);
    dist[1] = 0;
    
    for (int i = 1; i < N; i ++) {
        int t = -1;
        for (int j = 1; j < N; j ++)
            if (!st[j] && (t == -1 || dist[j] < dist[t]))
                t = j;
                
        st[t] = true;
        for (int j = max(t - 21, 1); j <= min(t + 21, N - 1); j ++)
            dist[j] = min(dist[j], dist[t] + t / __gcd(t, j) * j);
    }
    
    cout << dist[2021] << endl;
}

5.空间

题面:

小蓝准备用256MB 的内存空间开一个数组,数组的每个元素都是32 位二进制整数。

如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB 的空间可以存储多少个32 位二进制整数?

题解:

先将MB转换为字节Byte,也就是Byte(B),1MB = 1024KB, 1KB = 1024B,1B = 8bit(位)所以256 MB = 256 * 1024 * 1024B,32位二进制整数的存储空间也即32bit = 32 / 8 = 4B,所以可以存在 256 * 1024 * 1024 / 4,使用程序计算出结果为:67108864

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
int main()
{
    printf("%d", 256 * 1024 * 1024 / 4);
    return 0;
}

6.砝码称重

题面:

你有一架天平和N 个砝码,这N 个砝码重量依次是W1, W2, ... , WN。

请你计算一共可以称出多少种不同的重量?

注意砝码可以放在天平两边。

输入格式

输入的第一行包含一个整数N。

第二行包含N 个整数:W1, W2, W3, ... , WN。

对于50% 的评测用例,1 ≤ N ≤ 15。

对于所有评测用例,1 ≤ N ≤ 100,N 个砝码总重不超过100000。

输出格式

输出一个整数代表答案。

题解:

用dp法,分析状态和状态转移的过程

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
int n,w[105],sum=0,ans=0;
    int dp[102][100005];
int main()
{
    
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> w[i];
        sum += w[i];
    }
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++) 
    {   
        for (int j = 0; j <= sum; j++) 
        {
            dp[i][j] = dp[i - 1][abs(j - w[i])] + dp[i - 1][j + w[i]] + dp[i - 1][j];
        }
    }
    for (int i = 1; i <= sum; i++)
    {
        if (dp[n][i] != 0)
        {
            ans++;
        }
    }

    cout << ans;
    return 0;
}

7.括号序列

题面:

给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法。

当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。

两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。

例如,对于括号序列(((),只需要添加两个括号就能让其合法

有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和((()))。

输入格式

输入一行包含一个字符串s,表示给定的括号序列,序列中只有左括号和右括号。

对于40% 的评测用例,|s| ≤ 200。

对于所有评测用例,1 ≤ |s| ≤ 5000。

输出格式

输出一个整数表示答案,答案可能很大,请输出答案除以1000000007

题解:

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using LL=long long;
const int N = 5005;
int f[N][N];
int mod=1e9+7;
string s;
int n;
LL get(){
    memset(f,0,sizeof f);
    f[0][0]=1;
    for(int i=1;i<=n;i++){
        if(s[i-1]=='('){
            for(int j=1;j<=n;j++)
                f[i][j]=f[i-1][j-1];
        }
        else{
            f[i][0]=(f[i-1][1]+f[i-1][0])%mod;
            for(int j=1;j<=n;j++)
                f[i][j]=(f[i-1][j+1]+f[i][j-1])%mod;
        }
    }
    for(int i=0;i<=n;i++)
        if(f[n][i])
            return f[n][i];
    return -1;
}
int main(){
    cin>>s;
    n=s.size();
    LL x=get();
    reverse(s.begin(),s.end());
    for(int i=0;i<n;i++){
        if(s[i]==')')
            s[i]='(';
        else
            s[i]=')';
    }
    LL y=get();
    cout<<(x*y)%mod;
}

8.时间显示

题面:

小蓝要和朋友合作开发一个时间显示的网站。

在服务器上,朋友已经获取了当前的时间,用一个整数表示。

值为从1970 年1 月1 日00:00:00 到当前时刻经过的毫秒数。

现在,小蓝要在客户端显示出这个时间。

小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。

给定一个用整数表示的时间,请将这个时间对应的时分秒输出。

输入格式

输入第一行包含正整数T,表示存在T组测试数据,T不超过1000。

接下来T行,每行一个正整数表示时间。时间不超过10^18。

输出格式

输出T行,每行按照如下格式:

输出时分秒表示的当前时间,格式形如HH:MM:SS

其中HH 表示时,值为0 到23,MM 表示分,值为0 到59,SS 表示秒,值为0 到59。

时、分、秒不足两位时补前导0。

题解:

算出每天,每小时,以及每分钟的秒数,把毫秒换为秒,进行求解。

注意数据范围,应该使用longlong类型;

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
void solve()
{
    LL s,d=24*60*60,h=60*60,m=60,mm,hh,ss;
    cin >> s;
    s /= 1000;
    s %= d;
    hh = s / h;
    mm = s % h / m;
    ss = s % m;
    printf("%02d:%02d:%02d\n", hh, mm, ss);
}
int main()
{
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}

9.杨辉三角形

题面:

下面的图形是著名的杨辉三角形:

如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:

1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ...

给定一个正整数N,请你输出数列中第一次出现N 是在第几个数?

输入格式

输入包含T行,表示T组测试数据。T不超过10。

每行输入一个整数N,N不超过10^9。

输出格式

对于每组测试数据输出一行表示答案。

题解:

对于同一行,列数越大对应的数值也越大。而且某一行的某一列的值为x,在列数不变的情况下,无论行数怎么变大都不会再出现比x小的数;同理再行数不变的情况下列数怎么变大也不会出现比x小的数。并且得知n小于等于10的0次方时,有效列数为0-16列。因此我们可以一列一列的考虑,由于随着行号的变大,数值时单调递增的,其知道了行号、列号对应的数值也就知道了,于是便可以二分行号,使用二分查找的方法来计算本题。

杨辉三角形的行数符号公差为1的等差数列,故用等差数列求和公式

加上第几列再加上1(因为列从0开始)即可得出该数的位置

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
LL N;
LL C(int a, int b)
{
    LL res = 1;
    for (LL i = a, j = 1; j <= b; i--, j++)
    {
        res = res * i / j;
        if (res > N)
            return res;
    }
    return res;
}
void solve()
{
    cin >> N;
    for (int k = 16; k >= 0; k--)
    {
        LL l = 2 * k, r = max(N, l), mid;
        while (l <= r) {
            mid = l + (r - l) / 2;
            LL CC = C(mid, k);
            if (CC == N)
                break;
            else if (CC > N)
                r = mid - 1;
            else
                l = mid + 1;
        }
        if (C(mid, k) == N)
        {
            cout << (mid + 1) * mid / 2 + k + 1 << endl;
            break;
        }
    }
}
int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

10.双向排序

题面:

给定序列(a[1], a[2], ... , a[n]) = (1, 2, ... , n),即a[i] = i。

小蓝将对这个序列进行m次操作,每次可能是将a[1], a[2], ... a[qi] 降序排列,或者将a[qi], a[qi+1], ... , a[n] 升序排列。

请求出操作完成后的序列。

输入格式

输入的第一行包含两个整数n, m,分别表示序列的长度和操作次数。

接下来m行描述对序列的操作,其中第i行包含两个整数pi, qi 表示操作类型和参数。

当pi = 0 时,表示将a[1], a[2], ... a[qi] 降序排列;当pi = 1 时,表示将a[qi], a[qi+1], ... , a[n] 升序排列升序排列。

对于30% 的评测用例,n,m ≤ 1000;

对于60% 的评测用例,n,m ≤ 5000;

对于所有评测用例,1 ≤ n,m ≤ 100000, 0 ≤ ai ≤ 1,1 ≤ bi ≤ n。

输出格式

输出一行,包含n个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。

题解:

#include<stdio.h>
#include<iostream>
#include<string>
#include<cmath>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
vector<int> st[100005];
int a[100005];
int  main()
{
    int n, m, top = 0;
    cin >> n >> m;
    for (int i = 1; i <= m; i++)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        if (top == 0 && u == 0)
        {
            ++top;
            st[top].push_back(u);
            st[top].push_back(v);
        }
        else if (top)
        {
            if (u == st[top][0] && (v <= st[top][1] && u == 0 || v >= st[top][1] && u == 1))
                continue;
            else
            {
                if (u == 0 && u == st[top][0] && v > st[top][1])
                    st[top--].clear();
                if (u == 1 && u == st[top][0] && v < st[top][1])
                    st[top--].clear();
                if (u == 0)
                    while (top > 1 && st[top - 1][1] <= v)
                    {
                        st[top--].clear();
                        st[top--].clear();
                    }
                else
                    while (top > 1 && st[top - 1][1] >= v)
                    {
                        st[top--].clear();
                        st[top--].clear();
                    }
                ++top;
                st[top].push_back(u);
                st[top].push_back(v);
            }
        }
    }
    int temp = n;
    int l = 1, r = n;
    for (int i = 1; i <= top; i++)
    {
        if (i & 1)
        {
            for (int j = r; j > st[i][1]; j--)
                if (temp)
                    a[j] = temp--;
            r = st[i][1];
        }
        else
        {
            for (int j = l; j < st[i][1]; j++)
                if (temp)
                    a[j] = temp--;
            l = st[i][1];
        }
    }
    if (top & 1)
        for (int i = l; i <= r; i++)
            a[i] = temp--;
    else
        for (int i = r; i >= l; i--)
            a[i] = temp--;
    for (int i = 1; i <= n; i++)
        printf("%d ", a[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值