2021年第十二届蓝桥杯省赛真题 (C/C++ 大学B组)

目录

    • 题目A:空间(5分)
    • 题目B:卡片(5分)
    • 题目C:直线(10分)
      • 结构体排序的两种方法
    • 题目D:货物摆放(10分)
    • 题目E:路径(15分)
    • 题目F:时间显示(15分)
    • 题目G:砝码称重(20分)
    • 题目H:杨辉三角形
    • 题目I:双向排序(25分)
    • 题目J:括号序列

题目A:空间(5分)

  • 题目描述
    小蓝准备用256MB 的内存空间开一个数组,数组的每个元素都是 32 位二进制整数,如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB 的空间可以存储多少个32 位二进制整数?
    答案提交
      这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。67108864

  • 题目分析
    计算器:
    1MB=1024KB=10241024B=102410248bit
    256
    102410248/32=67108864

  • 题目代码

#include 
int main() {
    printf("%d", 256 >> 2 << 20);
}

题目B:卡片(5分)

  • 题目描述
    小蓝有很多数字卡片,每张卡片上都是数字0到9。
    小蓝准备用这些卡片来拼一些数,他想从1开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。
    小蓝想知道自己能从1拼到多少。
    例如,当小蓝有30张卡片,其中0到9各3张,则小蓝可以拼出1到10,但是拼11时卡片1已经只有一张了,不够拼出11。
    现在小蓝手里有0到9的卡片各2021张,共20210张,请问小蓝可以从1拼到多少?3181
    提示:建议使用计算机编程解决问题。

  • 题目分析

  • 题目代码(1)

#include 

int n, ans, cnt;

int main() {
    while (1) {
       n = ans;
       while (n) {
           if (n % 10 == 1) cnt++;
           n /= 10;
       }
       if (cnt < 2021) ans++;
       else break;
    }
    printf("%d", ans);
}
  • 题目代码(2)
#include 
#include 
#include 

using namespace std;
int s[10];

bool check(int x) {
  while (x) {
    int t = x % 10;
    x = x / 10;
    if (--s[t] < 0) return false;
  }
  return true;
}

int main() {
  for (int i = 0; i < 10; i++) s[i] = 2021;
  for (int i = 1;; i++) {
    if (!check(i)) {
      //检查该数是否已经用完,不够用即最后一个数为所求的数
      cout << i - 1 << endl;
      return 0;
    }
  }
}

题目C:直线(10分)

  • 题目描述
    在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上, 那么这些点中任意两点确定的直线是同一条。
    给定平面上 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​) 之 间的整数的点。
    请问这些点一共确定了多少条不同的直线。40257

  • 题目分析

  • 题目代码

//直线
//大致思路:依次枚举各个点,每两个点生成对应的斜率和截距。最后看有多少个不同的组合,即有多少条不同的直线
//注意事项:类型为double的两个数值a和b,即使数值相同,对应的double值也有可能不同,故在cpp中比较两个double值应判断其abs之差是否在很小的一个范围之内,例如1e-8
#include
#include
#include
#include
#include
using namespace std;
// 直线的最大数
const int N=200000;
int n=0;
struct Line
{
    double k;
    double b;
    // 结构体内嵌排序函数
    // 直接写比较函数是裸的len表示当前的值,如果len1e-8||fabs(l[i].b-l[i-1].b)>1e-8)
        res++;

    }
    cout<

结构体排序的两种方法

一、自定义比较函数

#include 
#include 
using namespace std;
struct Node{
	string name;
	int age;
};

bool cmp(const Node a, const Node b)
{
	return a.age < b.age; // < 就是前面的小于后面的,即从小到大排序 
}

int main()
{
	struct Node nodes[3];
	nodes[0].name = "zhang";
	nodes[0].age = 18;
	
	nodes[1].name = "wang";
	nodes[1].age = 20;
	
	nodes[2].name = "li";
	nodes[2].age = 19;
	
	sort(nodes, nodes + 3, cmp);
	for(int i = 0; i < 3; ++i) {
		cout << nodes[i].age << endl;
	}
} 

二、结构体内嵌比较函数

#include 
#include 
using namespace std;
struct Node{
	string name;
	int age;
	
	// 这里是cpp的运算符重载,重载了 <  
	bool operator < (const Node& a) const
	{
		return age < a.age;  // < 就是从小到大排序 
	} 
};


int main()
{
	struct Node nodes[3];
	nodes[0].name = "zhang";
	nodes[0].age = 18;
	
	nodes[1].name = "wang";
	nodes[1].age = 20;
	
	nodes[2].name = "li";
	nodes[2].age = 19;
	
	sort(nodes, nodes + 3);
	for(int i = 0; i < 3; ++i) {
		cout << nodes[i].age << endl;
	}
} 

题目D:货物摆放(10分)

  • 题目描述
    小蓝有一个超大的仓库,可以摆放很多货物。
    现在,小蓝有 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 位数字)时,总共有多少种方案?2430
    提示:建议使用计算机编程解决问题。

  • 题目分析
    先获得2021041820210418所有质因数,再通过质因数去组合从而获得所有的正约数,最后只需在所有的正约数找3个乘积为2021041820210418就行。

  • 题目代码

#include 
#include 
#include 
// 用vector来存储约数
#include 

using namespace std;

// n比较大,会爆因子
typedef long long LL;

int main()
{
    LL n;
    cin>>n;
    // 枚举定义一下所有约数
    vector d;
    //求n的所有约数个数,三层循环,枚举判断n=a*b*c
    for(LL i=1; i*i<=n; i++)
    {
        if(n%i==0)
        {
            d.push_back(i);
            if(n/i!=i)  d.push_back(n/i);
        }
    }
    cout<< d.size()<< endl;
    int res = 0;
    //枚举一下a,b,c
    for(auto a:d)
        for(auto b:d)
            for(auto c:d)
                if(a*b*c==n)
                    res++;
    cout<

题目E:路径(15分)

  • 题目描述
    小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图中的最短路径。
    小蓝的图由 2021 个结点组成,依次编号 1 至 2021。
    对于两个不同的结点 a, b,如果 a 和 b 的差的绝对值大于 21,则两个结点之间没有边相连;如果 a 和 b 的差的绝对值小于等于 21,则两个点之间有一条长度为 a 和 b 的最小公倍数的无向边相连。
    例如:结点 1 和结点 23 之间没有边相连;结点 3 和结点 24 之间有一条无向边,长度为 24;结点 15 和结点 25 之间有一条无向边,长度为 75。
    请计算,结点 1 和结点 2021 之间的最短路径长度是多少。10266837
    提示:建议使用计算机编程解决问题。

  • 题目分析

  • 题目代码
    (1)spfa

#include 
#include 
#include 

using namespace std;

const int N=2200,M=N*50;

int n;
int h[N],e[M],w[M],ne[M],idx;
int q[N],dist[N];
bool st[N];

int gcd(int a,int b)//欧几里得算法 辗转相除法(递归)求最大公约数
{
    return b ? gcd(b,a%b):a;
}

void add(int a,int b,int c)//添加一条边a->b,边权为c
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void spfa()//求1号点到n号点的最短距离
{
    int hh=0,tt=0;
    memset(dist, 0x3f,sizeof dist);
    dist[1]=0;
    q[tt ++]=1;
    st[1]=true;

    while(hh!=tt)
    {
        int t=q[hh ++];
        if(hh==N) hh=0;
        st[t]=false;

        for(int i=h[t]; i!=-1; i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                if(!st[j])  //如果队列已存在j,则不需要将j重复插入
                {
                    q[tt++]=j;
                    if(tt==N) tt=0;
                    st[j]=true;
                }
            }
        }
    }
}

int main()
{
    n=2021;
    memset(h,-1,sizeof h);
    for(int i=1; i<=n; i++)
    {
        for(int j=max(1,i-21); j<=min(n,i+21); j++)
        {
            int d=gcd(i,j);
            // 最小公倍数 两个数乘机/最大公约数 i*j/d
            add(i,j,i*j/d);
        }
    }
    spfa();
    printf("%d\n",dist[n]);
    return 0;
}

(2)动态规划

#include
using namespace std;
//辗转相除法(递归)求最大公约数 
int gcd(int a, int b)
{
	return b == 0 ? a : gcd(b,a%b);
 } 
 //求最小公倍数 
 int lcm(int a, int b)
 {
 	return a*b/gcd(a,b);
 }
int main()
{
	//动态规划开辟数组 
	int dp[3000] = {0};
	//求出到每一个点的最短路径,先从第二个点开始 
	for (int i = 2; i <= 2021; i++)
	{
		int minValue = 100000000;//警示****该值要开辟的足够大(至少大于该题答案,题主错在这里) 
		if (i - 21 > 0)//如果大于21,则从i-21开始 
		{
			for (int j = i-21; j < i; j++)
			{
				//到该点的距离为距离小于等于21的任意一点的最短距离(dp[j]) 加上任意一点
				//到该点的距离(即最小公倍数)
				//最短距离就是求出上述 距离的最小值 
				minValue = min(minValue,lcm(j,i) + dp[j]);
			}
		}
		else
		{
			//否则从1开始 
			for (int j = 1; j < i; j++)
			{
				    //同上 
					minValue = min(minValue,lcm(j,i) + dp[j]);
			}
		}
		//该点的最短距离为求得的最小值 
		dp[i] = minValue;
	}
	cout << dp[2021] << endl;
	return 0;
}

(3)代码

#include
using namespace std;

const int N=2021;

int f[N+1][N+1];

//最大公约数
int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

//最小公倍数
int lcm(int a,int b)
{
    return a*b/gcd(a,b);
}

int min(int a,int b)
{
    return a=u;v--)
        {
            f[u][v]=f[v][u]=lcm(u,v);
        }
    }

    for(int k=1;k<=N;k++)
    {
        for(int i=1;i<=N;i++)
        {
            for(int j=1;j<=N;j++)
            {
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
            }
        }
    }

    cout<

题目F:时间显示(15分)

  • 题目描述
    小蓝要和朋友合作开发一个时间显示的网站。在服务器上,朋友已经获取了当前的时间,用一个整数表示,值为从 1970 年 1 月 1 日 00 : 00 : 00到当前时刻经过的毫秒数。
      现在,小蓝要在客户端显示出这个时间。小蓝不用显示出年月日,只需显示出时分秒即可,毫秒也不用显示,直接舍去即可。
      给定一个用整数表示的时间,请将这个时间对应的时分秒输出。
    输入格式
      输入一行包含一个整数,表示时间。
    输出格式
      输出时分秒表示的当前时间,格式形如 HH :MM:SS,其中HH 表示时,值为 0到 23,MM 表示分,值为 0 到 59,SS 表示秒,值为 0 到 59。时、分、秒不足两位时补前导0。
    【测试用例】
    Input:
    46800999
    Output:
    13:00:00

  • 题目分析

  • 题目代码

#include 
#include 
#include 

using namespace std;

typedef long long LL;

int main()
{
    LL n;
    cin >> n;
    //1s=1000ms,先去除毫秒
    n /= 1000;
    // 每一天秒数=24*60*60=86400s,取出最后一天的秒数
    n %= 86400;
    int h = n / 3600;//小时
    //取出最后一天除了时以外的秒数
    n %= 3600;
    int m = n / 60; //分钟
    int s = n % 60; //秒
    printf("%02d:%02d:%02d\n", h, m, s);
    return 0;
}

题目G:砝码称重(20分)

  • 题目描述
    你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1, W2, · · · , WN。
    请你计算一共可以称出多少种不同的重量?
    注意砝码可以放在天平两边。
    【输入】
    输入的第一行包含一个整数 N。
    第二行包含 N 个整数:W1, W2, W3, · · · , WN。
    【输出】
    输出一个整数代表答案。
    【样例输入】
    3
    1 4 6
    【样例输出】
    10
    提示
    【样例说明】
    能称出的 10 种重量是:1、2、3、4、5、6、7、9、10、11。
    1 = 1;
    2 = 6 4 (天平一边放 6,另一边放 4);
    3 = 4 1;
    4 = 4;
    5 = 6 1;
    6 = 6;
    7 = 1 + 6;
    9 = 4 + 6 1;
    10 = 4 + 6;
    11 = 1 + 4 + 6。
    【评测用例规模与约定】
    对于 50% 的评测用例,1 ≤ N ≤ 15。
    对于所有评测用例,1 ≤ N ≤ 100,N 个砝码总重不超过 100000。
  • 题目分析
  • 题目代码

// dp:状态表示f(i,j)-->集合:只从前i个物品中选,且总重量为j的所有方案的集合;属性:是否为true
//    状态计算:不选wi(f(i-1,j))、选+wi(f(i-1,j-wi))、选-wi(f(i-1,j+wi))
//    f(n,1~n)
#include 
#include 
#include 

using namespace std;

// -m<=j<=m
const int N = 110, M = 200010, B = M / 2;

int n, m;
int w[N];
bool f[N][M];

int main()
{
    scanf("%d", &n);
    // m代表所有砝码总重量
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]), m += w[i];

    f[0][B] = true;
    // j可能是负数,都要加一个偏移量(足够大的数)
    for (int i = 1; i <= n; i ++ )
        for (int j = -m; j <= m; j ++ )
        {
            f[i][j + B] = f[i - 1][j + B];
            if (j - w[i] >= -m) f[i][j + B] |= f[i - 1][j - w[i] + B];
            if (j + w[i] <= m) f[i][j + B] |= f[i - 1][j + w[i] + B];
        }

    int res = 0;
    for (int j = 1; j <= m; j ++ )
        if (f[n][j + B])
            res ++ ;
    printf("%d\n", res);
    return 0;
}

题目H:杨辉三角形

  • 题目描述
    下面的图形是著名的杨辉三角形:
      如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
    2021年第十二届蓝桥杯省赛真题 (C/C++ 大学B组)_第1张图片
    1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1,
    给定一个正整数 N,请你输出数列中第一次出现 N是在第几个数?
    输入格式
      输入一个整数 N。
    输出格式
      输出一个整数代表答案。
    Input:
    6
    Output:
    13
  • 题目分析
  • 题目代码
#include 
#include 
#include 

using namespace std;

typedef long long LL;

int n;

LL C(int a, int b)
{
    LL res = 1;
    for (int i = a, j = 1; j <= b; i --, j ++ )
    {
        res = res * i / j;
        if (res > n) return res;
    }
    return res;
}

bool check(int k)
{
    LL l = k * 2, r = max((LL)n, l);
    while (l < r)
    {
        LL mid = l + r >> 1;
        if (C(mid, k) >= n) r = mid;
        else l = mid + 1;
    }
    if (C(r, k) != n) return false;

    cout << r * (r + 1) / 2 + k + 1 << endl;

    return true;
}

int main()
{
    cin >> n;
    for (int k = 16; ; k -- )
        if (check(k))
            break;
    return 0;
}

题目I:双向排序(25分)

  • 题目描述
    给定序列 (a1, a2, · · · , an) = (1, 2, · · · , n),即 ai = i。
    小蓝将对这个序列进行 m 次操作,每次可能是将 a1, a2, · · · , aqi 降序排列,或者将 aqi , aqi+1, · · · , an 升序排列。
    请求出操作完成后的序列。
    【输入】
    输入的第一行包含两个整数 n, m,分别表示序列的长度和操作次数。
    接下来 m 行描述对序列的操作,其中第 i 行包含两个整数 pi, qi 表示操作类型和参数。当 pi = 0 时,表示将 a1, a2, · · · , aqi 降序排列;当 pi = 1 时,表示将 aqi , aqi+1, · · · , an 升序排列。
    【输出】
    输出一行,包含 n 个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。
    【样例输入】
    3 3
    0 3
    1 2
    0 2
    【样例输出】
    3 1 2
  • 提示
    【样例说明】
    原数列为 (1, 2, 3)。
    第 1 步后为 (3, 2, 1)。
    第 2 步后为 (3, 1, 2)。
    第 3 步后为 (3, 1, 2)。与第 2 步操作后相同,因为前两个数已经是降序了。
    【评测用例规模与约定】
    对于 30% 的评测用例,n, m ≤ 1000;
    对于 60% 的评测用例,n, m ≤ 5000;
    对于所有评测用例,1 ≤ n, m ≤ 100000,0 ≤ ai ≤ 1,1 ≤ bi ≤ n。
  • 题目分析
  • 题目代码
#include 
#include 
#include 

#define x first
#define y second

using namespace std;

typedef pair PII;

const int N = 100010;

int n, m;
PII stk[N];
int ans[N];

int main()
{
    scanf("%d%d", &n, &m);
    int top = 0;
    while (m -- )
    {
        int p, q;
        scanf("%d%d", &p, &q);
        if (!p)
        {
            while (top && stk[top].x == 0) q = max(q, stk[top -- ].y);
            while (top >= 2 && stk[top - 1].y <= q) top -= 2;
            stk[ ++ top] = {0, q};
        }
        else if (top)
        {
            while (top && stk[top].x == 1) q = min(q, stk[top -- ].y);
            while (top >= 2 && stk[top - 1].y >= q) top -= 2;
            stk[ ++ top] = {1, q};
        }
    }
    int k = n, l = 1, r = n;
    for (int i = 1; i <= top; i ++ )
    {
        if (stk[i].x == 0)
            while (r > stk[i].y && l <= r) ans[r -- ] = k -- ;
        else
            while (l < stk[i].y && l <= r) ans[l ++ ] = k -- ;
        if (l > r) break;
    }
    if (top % 2)
        while (l <= r) ans[l ++ ] = k -- ;
    else
        while (l <= r) ans[r -- ] = k -- ;

    for (int i = 1; i <= n; i ++ )
        printf("%d ", ans[i]);
    return 0;
}

题目J:括号序列

  • 题目描述
     给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。
      两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
      例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。
    输入格式
      输入一行包含一个字符串 s ss,表示给定的括号序列,序列中只有左括号和右括号。
    输出格式
      输出一个整数表示答案,答案可能很大,请输出答案除以 1000000007(即 1 0 9 + 7 10^{9} + 7 109+7)的余数。
    Input:
    ((()
    Output:
    5
    评测用例规模与约定
      对于 40% 的评测用例,∣ s ∣ ≤ 200。
      对于所有评测用例,1 ≤ ∣ s ∣ ≤ 5000 。

  • 题目分析

  • 题目代码

#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 5010, MOD = 1e9 + 7;

int n;
char str[N];
LL f[N][N];

LL work()
{
    memset(f, 0, sizeof f);
    f[0][0] = 1;
    for (int i = 1; i <= n; i ++ )
        if (str[i] == '(')
        {
            for (int j = 1; j <= n; j ++ )
                f[i][j] = f[i - 1][j - 1];
        }
        else
        {
            f[i][0] = (f[i - 1][0] + f[i - 1][1]) % 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()
{
    scanf("%s", str + 1);
    n = strlen(str + 1);
    LL l = work();
    reverse(str + 1, str + n + 1);
    for (int i = 1; i <= n; i ++ )
        if (str[i] == '(') str[i] = ')';
        else str[i] = '(';
    LL r = work();
    printf("%lld\n", l * r % MOD);
    return 0;
}

你可能感兴趣的:(蓝桥杯,算法,c语言,c++)