2021 第十二届蓝桥杯 C/C++ B组 省赛 第一场 题解

3 篇文章 0 订阅
2 篇文章 0 订阅

试题 A: 空间

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

256MB = 256 * 1024 * 1024B 
32b = 4B

答案

256 * 1024 * 1024 / 4 = 67108864

试题 B: 卡片

本题总分:5 分
【问题描述】
小蓝有很多数字卡片,每张卡片上都是数字 0 到 9。
小蓝准备用这些卡片来拼一些数,他想从 1 开始拼出正整数,每拼一个,
就保存起来,卡片就不能用来拼其它数了。
小蓝想知道自己能从 1 拼到多少。
例如,当小蓝有 30 张卡片,其中 0 到 9 各 3 张,则小蓝可以拼出 1 到 10,
但是拼 11 时卡片 1 已经只有一张了,不够拼出 11。
现在小蓝手里有 0 到 9 的卡片各 2021 张,共 20210 张,请问小蓝可以从 1
拼到多少?
思路

直接拆数然后check。
注意到处理到3182时check失败,应该输出上一次check成功的结果。
复杂度O(n)

答案

3181

代码

#include <iostream>

using namespace std;

int cnt[10];

void work(int n) {
	while (n) {
		++cnt[n % 10];
		n /= 10;
	}
}

bool check(int n) {
	for (int i = 0; i < 10; ++i) {
		if (cnt[i] > n)return false;
	}
	return true;
}

int main() {
	for (int i = 1; i <= 1e9; ++i) {
		work(i);
		if (!check(2021)) {
			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) 之
间的整数的点。请问这些点一共确定了多少条不同的直线。
思路

枚举所有可能的直线,计算其斜率和截距并保存在vector中用于去重。
注意double的经度损失问题。
注意直线斜率不存在时的特殊情况。
复杂度O(n^3)

答案

40257

代码

#include <iostream>
#include <vector>
#include <utility>

using namespace std;

struct point {
	double x;
	double y;
};
struct line {
	double k;
	double b;
};
vector<point> points;
vector<line> lines;

bool check(double k, double b) {
	for (int i = 0; i < lines.size(); ++i) {
		if (abs(lines[i].k - k) < 1e-5 && abs(lines[i].b - b) < 1e-5) {
			return false;
		}
	}
	return true;
}

int main() {
	//生成所有点
	for (int x = 0; x <= 19; ++x) {
		for (int y = 0; y <= 20; ++y) {
			points.push_back({ double(x),double(y) });
		}
	}

	//任选两点组成一条线
	for (int i = 0; i < points.size(); ++i) {
		for (int j = i + 1; j < points.size(); ++j) {
			double k, b;
			//k=(y2-y1)/(x2-x1)
			//b=y1-kx1
			if (points[i].x == points[j].x) {//斜率不存在
				k = 0x3f3f3f3f;
				b = points[i].y;
			}
			else {//斜率存在
				k = (points[j].y - points[i].y) / (points[j].x - points[i].x);
				b = points[i].y - k * points[i].x;
			}
			if (check(k, b))lines.push_back({ k,b });
		}
	}
	cout << lines.size() << 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 位数字)时,总共有多少种
方案?
思路

O(sqrt(n))复杂度尝试因数分解,发现其只有128个因数,因此可以O(n^3)暴力枚举因数统计方案数。

答案

2430

代码

#include <iostream>
#include <cmath>
#include <unordered_map>
#include <vector>

using namespace std;

using ull = unsigned long long;

unordered_map<ull, bool> vis;
vector<ull> factors;

int main() {
	ull n = 2021041820210418ull;
	for (ull i = 1; i <= sqrt(n); ++i) {
		if (n % i == 0) {
			vis[i] = true;
			vis[n / i] = true;
		}
	}
	for (auto& [k, v] : vis) {
		factors.push_back(k);
	}
	ull ans = 0;
	for (auto& i : factors) {
		for (auto& j : factors) {
			for (auto& k : factors) {
				if (i * j * k == n) ++ans;
			}
		}
	}
	cout << ans << endl;
}

试题 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 之间的最短路径长度是多少。
思路

直接拿lcm建图,因为点只有2000个,直接floyd跑最短路即可。

答案

10266837

代码

#include <iostream>

using namespace std;

using ull = unsigned long long;

ull gcd(ull a, ull b) {
	return b ? gcd(b, a % b) : a;
}

ull lcm(ull a, ull b) {
	return a / gcd(a, b) * b;
}

ull dis[2025][2025];

int main() {
	memset(dis, 0x3f, sizeof dis);
	for (int i = 1; i <= 2021; ++i) {
		for (int j = i + 1; j <= 2021; ++j) {
			if (j - i <= 21) {
				dis[i][j] = min(lcm(i, j), dis[i][j]);
				dis[j][i] = min(lcm(j, i), dis[j][i]);
			}
			else {
				break;
			}
		}
	}
	for (int i = 1; i <= 2021; ++i) {
		for (int j = 1; j <= 2021; ++j) {
			for (int k = 1; k <= 2021; ++k) {
				dis[i][k] = min(dis[i][k], dis[i][j] + dis[j][k]);
			}
		}
	}
	cout << dis[1][2021] << endl;
}

上述算法复杂度O(n^3),跑的比较慢,另附上一个更快的版本。

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

using ull = unsigned long long;
const ull infinite = 0x3f3f3f3f3f3f3f3f;

ull gcd(ull a, ull b) {
	return b ? gcd(b, a % b) : a;
}

ull lcm(ull a, ull b) {
	return a / gcd(a, b) * b;
}

struct cmp {
	auto operator()(pair<int, int>& p1, pair<int, int>& p2) const {
		return p1.second > p2.second;
	}
};

vector<vector<pair<int, ull>>> edges;
ull dis[2022];

int main() {
	
	memset(dis, 0x3f, sizeof dis);
	edges.resize(2022);

	for (int i = 1; i <= 2021; ++i) {
		for (int j = i + 1; j <= 2021; ++j) {
			if (j - i <= 21) {
				edges[i].emplace_back(j, lcm(i, j));
				edges[j].emplace_back(i, lcm(i, j));
			}
			else {
				break;
			}
		}
	}

	priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> que;
	que.emplace(1, 0);

	while (!que.empty()) {
		auto [from, tot_cost] = que.top();
		que.pop();
		if (dis[from] == infinite) {
			dis[from] = tot_cost;
			for (auto& [to, cost] : edges[from]) {
				if (dis[to] == infinite) que.emplace(to, cost + tot_cost);
			}
		}
	}
	cout << dis[2021] << endl;
}

试题 F: 时间显示

时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
【问题描述】
小蓝要和朋友合作开发一个时间显示的网站。在服务器上,朋友已经获取
了当前的时间,用一个整数表示,值为从 1970 年 1 月 1 日 00:00:00 到当前时
刻经过的毫秒数。
现在,小蓝要在客户端显示出这个时间。小蓝不用显示出年月日,只需要
显示出时分秒即可,毫秒也不用显示,直接舍去即可。
给定一个用整数表示的时间,请将这个时间对应的时分秒输出。
【输入格式】
输入一行包含一个整数,表示时间。
【输出格式】
输出时分秒表示的当前时间,格式形如 HH:MM:SS,其中 HH 表示时,值
为 0 到 23,MM 表示分,值为 0 到 59,SS 表示秒,值为 0 到 59。时、分、秒
不足两位时补前导 0。
【样例输入 1】
46800999
【样例输出 1】
13:00:00
【样例输入 2】
1618708103123
【样例输出 2】
01:08:23
【评测用例规模与约定】
对于所有评测用例,给定的时间为不超过 1018 的正整数。
思路

时间除以1000得到秒数。
又因为不需要输出年月日,再将时间对24*60*60取模得到在一天内的秒数。
再根据该秒数计算时分秒。

代码

#include <iostream>
#include <cstdio>
using namespace std;
using ull = unsigned long long;

int main() {
    ull t;
    cin >> t;
    t /= 1000;
    t %= 24 * 60 * 60;
    int h, m, s;
    h = t / 60 / 60;
    m = t / 60 % 60;
    s = t % 60;
    printf("%.2d:%.2d:%.2d", h, m, s);
}

试题 G: 砝码称重

时间限制: 1.0s 内存限制: 256.0MB 本题总分: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。

思路

对于每个砝码,有三种状态:放左边,放右边,不放。
那么,爆搜的话复杂度达到O(3^n),只能过大约一半数据。
考虑动态规划,使用dp[i][j]表示考虑到第i个物品,此时天枰重量为j。
dp[i][j]的值表示是否可以达到这种状态。
状态转移方程如下:

d p [ i ] [ j ] = { d p [ i ] [ j ] ∣ d p [ i − 1 ] [ j ] , 不 选 第 i 个 砝 码 d p [ i ] [ j ] ∣ d p [ i − 1 ] [ j − v a l u e [ i ] ] , 第 i 个 砝 码 放 左 边 d p [ i ] [ j ] ∣ d p [ i − 1 ] [ j + v a l u e [ i ] ] , 第 i 个 砝 码 放 右 边 dp[i][j]=\left\{ \begin{aligned} dp[i][j]\mid dp[i-1][j], 不选第i个砝码\\ dp[i][j]\mid dp[i-1][j-value[i]],第i个砝码放左边\\ dp[i][j]\mid dp[i-1][j+value[i]],第i个砝码放右边 \end{aligned} \right. dp[i][j]=dp[i][j]dp[i1][j],idp[i][j]dp[i1][jvalue[i]],idp[i][j]dp[i1][j+value[i]],i

时间复杂度O(mn),其中m为砝码数量,n为砝码体积。
由于定义放左边是减去val[i],放右边是加上val[i],在过程中可能出现负值,一种解决办法是将初始重量0进行偏移,详见代码。

代码

#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;

bool dp[101][300001];
int n;
vector<int> v;
unordered_map<int, bool>ans;
const int shift = 150000;

int main() {
	cin >> n;

	for (int i = 0; i < n; ++i) {
		int temp;
		cin >> temp;
		v.push_back(temp);
	}

	//偏移量shift为150000,原因是砝码总重量最大100000,偏移后的砝码重量范围在[50000,250000],数组开300000即可

	//第0个砝码放右边,放左边,不放
	dp[0][shift + v[0]] = dp[0][shift - v[0]] = dp[0][shift] = true;

	for (int i = 1; i < n; ++i) {
		for (int j = 1; j <= 2 * shift; ++j) {
			dp[i][j] |= dp[i - 1][j];//不放第i个砝码
			dp[i][j] |= dp[i - 1][j + v[i]];//放右边
			if(j - v[i] >= 1) dp[i][j] |= dp[i - 1][j - v[i]];//放左边
		}
	}

	for (int i = 1; i <= 2 * shift; ++i) {
		if (dp[n - 1][i]) {
			ans[abs(shift - i)] = true;//砝码放左边和右边可能存在重复情况,使用unordered_map和abs去重
		}
	}
	
	cout << ans.size() - 1 << endl;//减去所有砝码都不选即砝码重量为0的特殊情况
}

另外,这题和牛客网:小M和天平是一样的,改一改偏移量就能过,顺便附上牛客这题的代码

#include <iostream>
#include <vector>
#include <unordered_map>
#include <cstring>

using namespace std;

bool dp[101][30001];
int n;
vector<int> v;
const int shift = 10005;

int main() {
    
	while(cin >> n){
        
        memset(dp,0,sizeof dp);
        v.clear();
        
        for (int i = 0; i < n; ++i) {
            int temp;
            cin >> temp;
            v.push_back(temp);
        }
        
        dp[0][shift + v[0]] = dp[0][shift - v[0]] = dp[0][shift] = true;

        for (int i = 1; i < n; ++i) {
            for (int j = 1; j <= 2 * shift; ++j) {
                dp[i][j] |= dp[i - 1][j];
                dp[i][j] |= dp[i - 1][j + v[i]];
                dp[i][j] |= dp[i - 1][j - v[i]];
            }
        }
        
        int m;
        cin>>m;
        for(int i=0;i<m;++i){
            int k;
            cin>>k;
            if(k>100*100){//最大重量只有10000
                cout<<"NO"<<endl;
                continue;
            }else{
                cout<<(dp[n-1][k+shift]?"YES":"NO")<<endl;
            }
        }
        
    }
}

  • 24
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值