2021第十二届蓝桥杯省赛第二场 C/C++ 大学 B 组

大家好呀,下面是我自己参加蓝桥杯的思路和代码,emm不一定对(十分欢迎指出错误,和提供新思路哇~~),选择题在群里对过答案了。有不一样的告诉下我呀,再验证下哈哈、一起学习。 --(这第二场的题,好像比第一场简单太多了,复习了几晚的dp,还是强行看错题才用出来一个…)…

试题 A: 求余 1

本题总分:5 分
【问题描述】
在 C/C++/Java/Python 等语言中,使用 % 表示求余,请问 2021%20 的
值是多少?
这题直接

cout << 2021%20<<endl;

试题 B: 双阶乘 59375

本题总分:5 分
【问题描述】
一个正整数的双阶乘,表示不超过这个正整数且与它有相同奇偶性的所有
正整数乘积。n 的双阶乘用 n!! 表示。
例如:
3!! = 3 × 1 = 3。
8!! = 8 × 6 × 4 × 2 = 384。
11!! = 11 × 9 × 7 × 5 × 3 × 1 = 10395。
请问,2021!! 的最后 5 位(这里指十进制位)是多少?
注意:2021!! = 2021 × 2019 × · · · × 5 × 3 × 1。
提示:建议使用计算机编程解决问题。
思路:直接暴力了

#include<iostream>
using namespace std;
const int mod = 1e5;
int main()
{
    int res = 1;
    for (int i = 1, j = 0; i <= 2021; i += 2) //59375
        res = res * i % mod;
    cout << res << endl;
    return 0;
}

试题 C: 格点 15698

本题总分:10 分
【问题描述】
如果一个点 (x, y) 的两维坐标都是整数,即 x ∈ Z 且 y ∈ Z,则称这个点为
一个格点。
如果一个点 (x, y) 的两维坐标都是正数,即 x > 0 且 y > 0,则称这个点在
第一象限。
请问在第一象限的格点中,有多少个点 (x, y) 的两维坐标乘积不超过 2021,
即 x · y ≤ 2021。

思路: 直接暴力

#include<iostream>
using namespace std;
const  int N = 2021;
int main()
{
    int res = 0;
    for (int x = 1; x <= N; ++ x)
        for (int y = 1; y <= N; ++ y)
            if (x * y <= N)    ++res;
    cout << res << endl;        //15698
    return 0;
}

试题 D: 整数分解 691677274345

本题总分:10 分
【问题描述】
将 3 分解成两个正整数的和,有两种分解方法,分别是 3 = 1 + 2 和
3 = 2 + 1。注意顺序不同算不同的方法。
将 5 分解成三个正整数的和,有 6 种分解方法,它们是 1+1+3 = 1+2+2 =
1 + 3 + 1 = 2 + 1 + 2 = 2 + 2 + 1 = 3 + 1 + 1。
请问,将 2021 分解成五个正整数的和,有多少种分解方法?

emm这题也是可以直接暴力的,四重循环也是可以跑完的哈哈。
不过还是优化下比较快,先计算三个数的和,再计算两个数的和即可。
顺序不同算不同的方法!!emm呜呜希望大家别和我一样看成顺序不同是同样的方法用了多重背包呜呜呜

#include<iostream>
using namespace std;
const int N = 2021;
int ha[N];
int main()
{
    long long res = 0;
    for (int i = 1; i < N; ++ i) {
        for (int j = 1; j < N ; ++ j) {
            if (i + j >= N) break;      // 剪枝
            for (int k = 1; k < N; ++ k) {          // 先保存三个数的和
                if ( i + j + k < N)
                    ha[ i + j + k] ++;
                else break;	//剪枝
            }
        }
    }
    for (int i = 1; i < N; ++ i) {      //再 枚举 后面两个数
        for (int j = 1; j < N; ++ j) {
            int x = N - (i + j);
            if (x <= 0)   break;
            res += ha[x];
        }
    }
    cout << res << endl; //691677274345
    return 0;
}

试题 E: 城邦 4046

本题总分:15 分
【问题描述】
小蓝国是一个水上王国,有 2021 个城邦,依次编号 1 到 2021。在任意两
个城邦
之间,都有一座桥直接连接。
为了庆祝小蓝国的传统节日,小蓝国政府准备将一部分桥装饰起来。
对于编号为 a 和 b 的两个城邦,它们之间的桥如果要装饰起来,需要的费
用如下计算:找到 a 和 b 在十进制下所有不同的数位,将数位上的数字求和。
例如,编号为 2021 和 922 两个城邦之间,千位、百位和个位都不同,将这
些数位上的数字加起来是 (2 + 0 + 1) + (0 + 9 + 2) = 14。注意 922 没有千位
千位看成 0。为了节约开支,小蓝国政府准备只装饰 2020 座桥,并且要保证从任意一个
城邦到任意另一个城邦之间可以完全只通过装饰的桥到达。
请问,小蓝国政府至少要花多少费用才能完成装饰。
提示:建议使用计算机编程解决问题。

显然的最小生成树问题:当时用Kruskal跑得还是很快的。

#include<iostream>
#include<algorithm>
using namespace std;
const int N =2030;
int n =2021;
struct nd{			//Kruskal 只用边即可
	int x, y , z;
	bool operator < (const nd &  t)const{
		return z  < t.z;
	}
}e[N*N*3];

string toString(int a)		// 当时我们比赛那个机房有点问题 不能用to_stirng 硬是找不到,只能手写了
{
	string s;
	while(a){
		s += (a%10)+'0';
		 a /=10;	 
	}
	reverse(s.begin(),s.end());
	return s;
}
int cal(int a,int b)		// 统计 每个位不同元素 再求总和
{
	int res = 0;
	string sa = toString(a), sb =toString(b);	

	while(sa.size()<sb.size()) sa = "0" + sa;		// 因为 枚举时 a <b 所以 只可能是a要补前导0
	for(int i = 0; i < sa.size(); ++ i){
		if(sa[i] != sb[i])
			res += sa[i] -'0' + sb[i] -'0';
	}
	return res;
}

int p[N];
int find(int x){
	if(x != p[x]) return p[x] = find(p[x]);
	return p[x];
}

int main()
{
	int cnt = 0;
	for(int i = 1; i <= n ;++ i){
		for(int j = i + 1;j <=n;++ j){
			int w = cal(i ,j);
			e[cnt++] = {i,j,w};		//这题 有向边 和五向边的做法 结果是一样的emm
			e[cnt++] = {j,i,w};
		}
	}	
	for(int i = 1; i <= n;++i) p[i] = i;		//并查集初始化
	sort(e,e + cnt);			//先sort
 	int res = 0;
 	int c= 0;

	for(int i = 0;i < cnt; ++ i){			//Kruskal
		int a = e[i].x, b = e[i].y , w = e[i].z;
		int pa = find(a) , pb = find(b);
		if(pa != pb){			//一直合并即可  
			p[pa] = pb;
			res += w;
			++c;			//合并n - 1次
		}
	}
	cout <<c<<endl;		//2020
	cout << res <<endl;		// 4046
	cout << (4046 ==res) <<endl;
	return 0;
}

试题 F: 特殊年份

时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
【问题描述】
今年是 2021 年,2021 这个数字非常特殊,它的千位和十位相等个位比
百位大 1
,我们称满足这样条件的年份为特殊年份。
输入 5 个年份,请计算这里面有多少个特殊年份。
【输入格式】
输入 5 行,每行一个 4 位十进制数(数值范围为 1000 至 9999),表示一个
年份。
【输出格式】
输出一个整数,表示输入的 5 个年份中有多少个特殊年份。
【样例输入】
2019
2021
1920
2120
9899
【样例输出】
2
【样例说明】
2021 和 9899 是特殊年份,其它不是特殊年份。

思路: 这题全部看下来 也就一个chck条件 q == shi && g - b == 1 ,直接枚举check就好啦

#include<iostream>
using namespace std;
int main()
{
    int n = 5;
    int res = 0;
    string s;
    for (int i = 0;  i < n ; ++ i) {
        cin >> s;
        int q = s[0] - '0', b = s[1] - '0', shi = s[2] - '0', g = s[3] - '0';
        res += (q == shi && g - b == 1 );           //这题就这一个判断条件。.. 枚举check 即可
    }
    cout << res << endl;
    return 0;
}

试题 G: 小平方

时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
小蓝发现,对于一个正整数 n 和一个小于 n 的正整数 v,将 v 平方后对 n
取余可能小于 n 的一半,也可能大于等于 n 的一半。
请问,在 1 到 n − 1 中,有多少个数平方后除以 n 的余数小于 n一半
例如,当 n = 4 时,1, 2, 3 的平方除以 4 的余数都小于 4 的一半。
又如,当 n = 5 时,1, 4 的平方除以 5 的余数都是 1,小于 5 的一半。而
2, 3 的平方除以 5 的余数都是 4,大于等于 5 的一半。
【输入格式】
输入一行包含一个整数 n。
【输出格式】
输出一个整数,表示满足条件的数的数量。
【样例输入】
5
【样例输出】
2
【评测用例规模与约定】
对于所有评测用例,1 ≤ n ≤ 10000。

思路: 因为n只有1万而已,直接O(n) 枚举即可。

#include<iostream>
using namespace std;
int main()
{
    int n ;
    cin >> n;
    double bn = double(n) / 2;      // 题目 没说怎么取整 所以就 直接当double算了
    int res = 0;
    for (int i = 1; i < n ; ++ i) { //n最大也就只有 一万 爆不了 int
        if ( i * i  % n < bn)       //枚举统计即可
            res++;
    }
    cout << res << endl;
    return 0;
}

试题 H: 完全平方数

时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
一个整数 a 是一个完全平方数,是指它是某一个整数的平方,即存在一个
整数 b,使得 a = b^2。
给定一个正整数 n,请找到最小的正整数 x,使得它们的乘积是一个完全平
方数。
【输入格式】
输入一行包含一个正整数 n。
【输出格式】
输出找到的最小的正整数 x。
【样例输入 1】
12
【样例输出 1】
3
【样例输入 2】
15
【样例输出 2】
15
【评测用例规模与约定】
对于 30% 的评测用例,1 ≤ n ≤ 1000,答案不超过 1000。
对于 60% 的评测用例,1 ≤ n ≤ 10^8,答案不超过 10^8。
对于所有评测用例,1 ≤ n ≤ 10^12,答案不超过 10^12。
思路 :从小到大用i的平方来枚举 然后i*i/n 就是答案了 这样可以O(n)做出来,但是也会超时emm,没想到好的优化方法,貌似可以用质因数,还是没想到…求助巨佬
后来想到了,这题就是让 n*x的质因子 的个数都是偶数即可,先质因数分解n,x 初始化为1,然后枚举质因数个数,是奇数个,则x * = (这个质因数)即可。

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

//12345678910			// 测这个数据 就测不动了 ,但是没想到 好的优化方法...
const int INF = 0x3f3f3f3f;
const LL N =1e12 +10;
int main()
{
	LL n;
	cin >> n;
	LL l = 1, r = N;
	while(l < r){		//先大致找到 第一个 大于等于 N 的 i*i emm 实际上这个 优化并帮不了什么忙的感觉...
		LL mid = l + r >> 1;
		if(mid * mid >= n ) r = mid;
		else l = mid + 1;
	}
	for(LL i = r; i <= n ; ++ i){		//然后直接平方枚举 还是得 O(n)  
		if( i * i  % n  ==0){
			cout <<i  * i / n <<endl;
			break;
		}
	}
	return 0;
}

试题 I: 负载均衡

时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分
【问题描述】
有 n 台计算机,第 i 台计算机的运算能力为 vi。
有一系列的任务被指派到各个计算机上,第 i 个任务在 ai 时刻分配,指定
计算机编号为 bi ,耗时为 ci 且算力消耗为 di 。如果此任务成功分配,将立刻
开始运行,期间持续占用 bi 号计算机 di 的算力持续 ci 秒。
对于每次任务分配,如果计算机剩余的运算能力不足则输出 −1,并取消这
次分配
,否则输出分配完这个任务后这台计算机的剩余运算能力
【输入格式】
输入的第一行包含两个整数 n, m,分别表示计算机数目和要分配的任务数。
第二行包含 n 个整数 v1, v2, · · · vn,分别表示每个计算机的运算能力。
接下来 m 行每行 4 个整数 ai
, bi
, ci
, di,意义如上所述。数据保证 ai 严格增,即 ai < ai+1。
【输出格式】
输出 m 行,每行包含一个数,对应每次任务分配的结果。
【样例输入】
2 6
5 5
1 1 5 3
2 2 2 6
3 1 2 3
4 1 6 1
5 1 3 3
6 1 3 4
【样例输出】
2
-1
-1
1
-1
0
【样例说明】
时刻 1,第 1 个任务被分配到第 1 台计算机,耗时为 5 ,这个任务时刻 6
会结束,占用计算机 1 的算力 3。
时刻 2,第 2 个任务需要的算力不足,所以分配失败了。
时刻 3,第 1 个计算机仍然正在计算第 1 个任务,剩余算力不足 3,所以
失败。
时刻 4,第 1 个计算机仍然正在计算第 1 个任务,但剩余算力足够,分配
后剩余算力 1。
时刻 5,第 1 个计算机仍然正在计算第 1, 4 个任务,剩余算力不足 4,失
败。
时刻 6,第 1 个计算机仍然正在计算第 4 个任务,剩余算力足够,且恰好
用完。
【评测用例规模与约定】
对于 20% 的评测用例,n, m ≤ 200。
对于 40% 的评测用例,n, m ≤ 2000。
对于所有评测用例,1 ≤ n, m ≤ 200000,1 ≤ ai
, ci
, di
, vi ≤ 109,1 ≤ bi ≤ n。
思路:感觉这题还是在读懂题目上占了不少时间,算法难度上并不是很大。
因为是 按照时间ai 递增给出的数据,所以我也顺着时间线往后走了,用一个heap来维持上一次剩余的体力的状态,并且只在下一个数据要进来时才去更新heap,即把已经算完的任务移除掉,并收回算力。,(这种方式蛮像操作系统中的内存写回hh),再检查这个新来的状态即可。

#include<iostream>
#include<queue>
using namespace std;
using PII = pair<int, int>;

const int INF = 0x3f3f3f3f;
const int N = 200010;
int n, m ;
			// 每个 计算机id   存着一个pair  , first 为 剩余算力值, second 为  按 pair <结束时间 , 耗费体力值>排序的小根堆。
pair<int, priority_queue<PII, vector<PII>, greater<PII> >> ha[N];

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1 ; i <= n; ++ i)
        scanf("%d", &ha[i].first);		//先给每个计算机 id 赋上体力值

    int a, b, c, d;
    for (int i = 0; i < m; ++ i) {
        scanf("%d%d%d%d", &a, &b, &c, &d);
        auto &pq = ha[b].second;		//	取到 id 对应的 优先队列
        while (!pq.empty() && pq.top().first <= a ) {		//先把每次 到了 结束时间 的都出队。
            ha[b].first +=  pq.top().second;			//加上直之前耗费的 的 算力值
            pq.pop();
        }
        if (ha[b].first >= d) {		//	如果 够算力
            pq.push({a + c, d});		//{结束时刻  , 耗费算力}
            ha[b].first -= d;			//当前体力 -=d
            cout <<  ha[b].first << '\n';		
        }
        else puts("-1");
    }
    return 0;
}

试题 J: 国际象棋

时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分
【问题描述】
众所周知,“八皇后” 问题是求解在国际象棋棋盘上摆放 8 个皇后,使得两
两之间互不攻击的方案数。已经学习了很多算法的小蓝觉得 “八皇后” 问题太简
单了,意犹未尽。作为一个国际象棋迷**,他想研究在 N × M 的棋盘上,摆放 K
个马**,使得两两之间互不攻击多少种摆放方案。由于方案数可能很大,只需
计算答案除以 1000000007 (即 109 + 7) 的余数。
如下图所示,国际象棋中的马摆放在棋盘的方格内,走 “日” 字,位于 (x, y)
格的马(第 x 行第 y 列)可以攻击 (x + 1, y + 2)、(x + 1, y − 2)、(x − 1, y + 2)、(x − 1, y − 2)、(x + 2, y + 1)、(x + 2, y − 1)、(x − 2, y + 1) 和 (x − 2, y − 1) 共 8 个
格子。
【输入格式】
输入一行包含三个正整数 N, M, K,分别表示棋盘的行数、列数和马的个
数。

【输出格式】
输出一个整数,表示摆放的方案数除以 1000000007 (即 109 + 7) 的余数。
【样例输入】
1 2 1
【样例输出】
2
【样例输入】
4 4 3
【样例输出】
276
【样例输入】
3 20 12
【样例输出】
914051446
【评测用例规模与约定】
对于 5% 的评测用例,K = 1;
对于另外 10% 的评测用例,K = 2;
对于另外 10% 的评测用例,N = 1;
对于另外 20% 的评测用例,N, M ≤ 6,K ≤ 5;
对于另外 25% 的评测用例,N ≤ 3,M ≤ 20,K ≤ 12;
对于所有评测用例,1 ≤ N ≤ 6,1 ≤ M ≤ 100,1 ≤ K ≤ 20。

思路:**和八皇后一样,直接回溯暴力了。**当时其实想到了状压dp优化(晚上试一下行不行),但是没多少时间了,又是后面又累,啧啧啧就没写出状严…

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
using PII = pair<int,int>;
const int mod = 1000000007;
const int N =10,M = 110;

int n, m , k;
int dire[8][2] ={{1,2},   {1,-2},   {-1,2},   {-1,-2},   {2,1},  {2,-1},  {-2,1}  ,{-2,-1}  };   //转移位置
int st[N][M];		//st[i][j] = true 表示 不能放 马,否则可以放
int res = 0;

void dfs(int x,int y,int num)			//像数独一样枚举即可  从左到右,从上到下
{
	if(x == n && y == 0)		//终点
		return ;
	
	dfs(x + (y +1) / m, (y + 1) % m,num);			//不放 马
	
	if(st[x][y] == 0){			//可以 放 马
		if(num + 1 == k ){				// 结果达成
			res = (res + 1) % mod;
			return ;
		}
		vector<PII> path;			//记录 这个放了 这个马 之后引起的变化
		st[x][y] = true;		//马的位置肯定是true
		for(int i = 0; i < 8; ++ i){
			int xi = dire[i][0] + x, yi = dire[i][1] + y;
			if(xi < n && xi >= 0 && yi < m && yi >= 0 && st[xi][yi] == 0){
				path.push_back({xi,yi});
				st[xi][yi] = 1;
			}	
		}
		
		dfs(x + (y + 1)/m, (y + 1) %m,num + 1);		//访问
		st[x][y] =false;			//回溯
		for(auto  &a : path)		//回溯
			st[a.first][a.second] = 0;
	}
}

int main()
{
	cin >> n >> m >> k;
	dfs(0,0,0);

	cout <<res <<'\n';
	return 0;
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值