【蓝桥杯】第十二届蓝桥杯C++B 组真题

发布时间:2023-11-18 10:30

第十二届蓝桥杯C++B 组真题

试题A. 空间

小蓝准备用256MB的内存空间开一个数组,数组的每个元素都是32 位二进制整数。
如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB 的空间可以存储多少个32 位二进制整数?


1MB= 1024KB 1KB= 1024B 1B= 1b

#include 
using namespace std;
int main()
{
	cout << 256 * 1024 * 1024 / 4;
	return 0;
}

试题B 卡片

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


依次取出每个数的每个位数,

注意:当小于0的时候说明当前数已经算过了i,所以i需要减1, 可以用个小数模拟一下

#include 

using namespace std;
int a[11];
bool check(int n)
{
	while(n)
	{
		int t = n % 10;
		if(-- a[t] < 0) return true;
		n /= 10;
	}
	return false;
}
int main()
{
	for(int i = 0; i < 10; i ++) a[i] = 2021;
	for(int i = 1; ; i ++)
	{
		if(check(i))
		{
			cout << i - 1;
			return 0;
		}
	} 
}

试题C 直线

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


set判重写法

#include
#include
#include
#include
using namespace std;

typedef pair PII;

set> s; //约分后去重 

vector v;

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

int main(){
	
	for(int i = 0; i < 20; i++)
		for(int j = 0; j < 21; j++ )
			v.push_back({i,j}); //读入所有点 
			
	for(int i = 0; i < v.size(); i++)
	{
		for(int j = i + 1; j < v.size(); j++)
		{
			int x1 = v[i].first, y1 = v[i].second;
			int x2 = v[j].first, y2 = v[j].second;
			int A = x2 - x1, B = y1 - y2, C = x1 * y2 - x2 * y1;
			int d = gcd(gcd(A,B), C);
			s.insert({ { B / d, A / d }, C / d }); //读入约分后的斜率和常数set自动判重	
 		}
	}
	
	cout << s.size();
	return 0;
} 

排序double写法,有精度问题

#include 
#include 
#include 
#include 

using namespace std;

const int N = 200000;

int n;
struct Line
{
    double k, b;
    bool operator< (const Line& t) const
    {
        if (k != t.k) return k < t.k;
        return b < t.b;
    }
}l[N];

int main()
{
    for (int x1 = 0; x1 < 20; x1 ++ )
        for (int y1 = 0; y1 < 21; y1 ++ )
            for (int x2 = 0; x2 < 20; x2 ++ )
                for (int y2 = 0; y2 < 21; y2 ++ )
                    if (x1 != x2)
                    {
                        double k = (double)(y2 - y1) / (x2 - x1);
                        double b = y1 - k * x1;
                        l[n ++ ] = {k, b};
                    }

    sort(l, l + n);
    int res = 1;
    for (int i = 1; i < n; i ++ )
        if (fabs(l[i].k - l[i - 1].k) > 1e-8 || fabs(l[i].b - l[i - 1].b) > 1e-8)
            res ++ ;
    cout << res + 20 << endl; //加上斜率为0的二十个竖线

    return 0;
}

试题D 货物摆放

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


前置条件就是a, b, c均会被n整除,所以可以晒出约数后暴力,(直接暴力时间太长。。。)

#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

int main()
{
    LL n = 2021041820210418;
    vector d;
    //约数板子
    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);
        }

    int res = 0;
    for (auto a: d)
        for (auto b: d)
            for (auto c: d)
                if (a * b * c == n)
                    res ++ ;
    cout << res << endl;

    return 0;
}

试题E 路径

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


模板默写题

#include 
#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 ++ ;
}

int spfa()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    queue q;
    q.push(1);
    st[1] = true;

    while (q.size())
    {
        int t = q.front();
        q.pop();

        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])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return dist[n];
}


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);
            add(i, j, i * j / d);
        }

    printf("%d\n", spfa());
    return 0;
}

试题F 时间显示

小蓝要和朋友合作开发一个时间显示的网站。
在服务器上,朋友已经获取了当前的时间,用一个整数表示。
值为从1970 年1 月1 日00:00:00 到当前时刻经过的毫秒数。
现在,小蓝要在客户端显示出这个时间。
小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。
给定一个用整数表示的时间,请将这个时间对应的时分秒输出。


思路:只需考虑某天时间,与日历问题均无关

#include 
#include 
#include 

using namespace std;

typedef long long LL;

int main()
{
    LL n;
    cin >> n;
    n /= 1000;
    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 砝码称重

题目描述

你有一架天平和N 个砝码,这N 个砝码重量依次是W1, W2, … , WN。
请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边。

输入格式

输入的第一行包含一个整数N。
第二行包含N 个整数:W1, W2, W3, … , WN。
对于50% 的评测用例,1 ≤ N ≤ 15。
对于所有评测用例,1 ≤ N ≤ 100,N 个砝码总重不超过100000。

输出格式

输出一个整数代表答案。

输入样例

3
1 4 6

输出样例

10

【蓝桥杯】第十二届蓝桥杯C++B 组真题_第1张图片


#include 
#include 
#include 

using namespace std;

const int N = 110, M = 200010, B = M / 2;

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

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]), m += w[i];

    f[0][B] = true;
    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];
            //同f[i][j + B] = f[i - 1][j + w[i] + B] || f[i][j + B];
        }

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

试题 H 杨辉三角形

题目描述

下面的图形是著名的杨辉三角形:
【蓝桥杯】第十二届蓝桥杯C++B 组真题_第2张图片
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
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。

输出格式

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

输入样例

2
3
6

输出样例

8
13

思路:见代码

#include 
#include 
#include 

using namespace std;

typedef long long LL;

int n;
/*
	找第一个,而杨辉三角是对称的所以不可能在右面
                    1  --- C(0, 0)
                  1 
                1   2  --- C(2, 1)
              1   3                             
            1   4   6  --- C(4, 2)
          1   5   10
        1   6   15  20 --- C(6, 3)
        ....
  	因此可以得出结论枚举斜行来找到值
  	列如:第二行 2 3 4 5 6 因数列是有序单调的所以可以用二分求解
*/
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); // 第一行特殊情况, r必须比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;
	//根据坐标(r,k)求出位置
    cout << r * (r + 1) / 2 + k + 1 << endl;

    return true;
}

int main()
{
    cin >> n;
    // n最大1e9,C(34, 17) > 1e9, C(32, 16) < 1e9,因此只要枚举前16个斜行即可
    for (int k = 16; ; k -- )
        if (check(k))
            break;
    return 0;
}

试题I 双向排序

题目描述

给定序列(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个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。

输入样例

3 3
0 3
1 2
0 2

输出样例

3 1 2

思路:假设这是我们的原序列
在这里插入图片描述

优化一: 由于一开始的序列是升序的,所以如果一开始的操作是后缀操作的话是没有意义的,序列是不会改变的,所以我们从前缀操作开始看,红色为将要操作的前缀序列
【蓝桥杯】第十二届蓝桥杯C++B 组真题_第3张图片

如果有连续的前缀操作,我们发现只需要进行最长的一个前缀操作即可,因为短的前缀操作后,长的还是要进行操作,为何不直接进行最长的前缀操作呢,后缀操作同理,我们把所有的操作节点存进栈,有两个成员变量,一个是当前操作是前缀操作还是后缀操作,另一个是操作的边界

优化二: 若进行到下图这种情况时
【蓝桥杯】第十二届蓝桥杯C++B 组真题_第4张图片

蓝色为原序列,红色为最长连续前缀,橙色为最长连续后缀

从下图我们发现

  • 原序列 AA 段严格大于 BB 段
  • AA 段 == A1A1 段, BB 段 == B1B1 段
  • 所以 A1A1 段严格大于 B1B1 段
  • A2A2 段 == A1A1 段
  • 所以 A2A2 段严格大于 CC 段,所以后缀升序操作(橙色)只需要操作 CC 段即可
  • 【蓝桥杯】第十二届蓝桥杯C++B 组真题_第5张图片

对于前缀操作同理 ,只需要操作 CC 段即可
【蓝桥杯】第十二届蓝桥杯C++B 组真题_第6张图片

优化三: 当出现下面这种情况时
【蓝桥杯】第十二届蓝桥杯C++B 组真题_第7张图片

也就是在进行一次前缀操作和后缀操作后,下一次的前缀操作在上一次的前缀操作的节点后,这个时候我们可以把前两次操作给删去,直接进行这一次的前缀操作,因为上一次的后缀操作和前缀操作都包含在了这一次的前缀操作内,前两次操作等于是没用的,所以我们只需要保留当前操作即可

另外,我们可以发现在我们一次次操作的过程中,操作的区间是在慢慢变小的,每次操作的时候,序列总有一部分是不需要进行操作的,我们也就可以用一个变量来递减的填入数组中

#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,表示给定的括号序列,序列中只有左括号和右括号。
对于40% 的评测用例,|s| ≤ 200。
对于所有评测用例,1 ≤ |s| ≤ 5000。

输出格式

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

输入样例

((()

输出样例

5

思路:考虑到我们只能在空隙中插入括号, 如果我们添加的一对左右括号不是在同一个空隙中, 那么他们显然是互不干扰的;如果是添加在同一个空隙中, 那么他们的添加顺序是唯一的, 只能是)(, 因为如果是()的话, 那我们本次的添加就是无效的, 不满足添加最少的括号使得序列得到匹配。 由此可得, 我们只需要单独计算出添加左括号的方案数, 乘上单独添加右括号的方案数就是答案的数量

【蓝桥杯】第十二届蓝桥杯C++B 组真题_第8张图片

#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]; //前i个括号左括号比右括号多j个的集合

LL work()
{
    //初始化
    memset(f, 0, sizeof f);
    f[0][0] = 1;
    for (int i = 1; i <= n; i ++ )
        if (str[i] == '(')//如果是左括号就是之前状态的j - 1
        {
            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][0] + f[i - 1][1] + ...... + f[i - 1][j]

		f[i][j + 1] = f[i - 1][0] + f[i - 1][1] + ...... + f[i - 1][j] + f[i - 1][j + 1]

		那么我们可以得出

		f[i][j] = f[i][j - 1] + f[i - 1][j - 1]
		*/
                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;
}

参考:

acwing 《蓝桥杯C++AB组辅导课》

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号