第十二届蓝桥杯C++B组省赛第一场复盘

试题 A: 空间

image-20210502013749927

直接用计算器算。

每个元素,32bit,也就是4Byte

256 ∗ 1024 ∗ 1024 / 4 = 67 , 108 , 864 256*1024*1024/4=67,108,864 25610241024/4=67,108,864

所以答案是67108864

试题 B: 卡片

image-20210502013804705
答案:3181

思路:模拟一下。

代码:

/*
3181
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

int s[10];	//手上的库存

bool check(int num) {
	//取每一位
	while(num) {
		int t = num%10;
		if(--s[t] < 0) return false;
		num/=10;
	}
	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; 
		}
	}
	return 0;
}

试题 C: 直线

image-20210502203553986

思路:通过斜率和截距可以唯一确定一条直线,也就是 y = k x + b y=kx+b y=kx+b

所以我们可以枚举所有点对能够构成直线的斜率k和截距b,然后判断有多少对不同的k和b。

特殊情况:当直线是垂直的时候,斜率不存在,所以需要特判垂直的直线。那么所有垂直的线都不枚举,最后给它直接加上个20就可以了(总共有20条垂线)。

由于浮点数的不准确性,所以不能直接判断k、b是否相等,而是需要判断是否在一个足够小的范围内(通常就设定为1e-8以内)。

答案:40257

代码:

/*
40257
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;

//最多有420个点,取个平方176,400
const int N = 200000;
int n;	//记录了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) continue;//跳过垂直线 
					double k = (double)(y2-y1)/(x2-x1);
					double b = y1 - k*x1;
					l[n++] = {k,b};
				}

	sort(l,l+n);
	int ans = 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)	//说明不是一条线
			ans++;
			
	ans += 20;	//加上没有斜率的20条垂线 
	cout << ans << endl;
	return 0;
}

试题 D: 货物摆放

image-20210502203530275

思路:找 n = a ∗ b ∗ c n=a*b*c n=abc的方案数,考虑顺序

方法一:纯暴力(等几秒出结果)

方法二:求n的所有约数,三重循环枚举所有选择,其实也很暴力。

答案:2430

方法一纯暴力代码:

/*
2430
纯暴力
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;

int main() {
	LL n = 2021041820210418;
	int res = 0;
	
	//先找出n=i*j*k的所有的组合
	for(LL i = 1; i*i*i <= n; i++) {	//i从1遍历到3次根号n
		if(n%i != 0) continue;	//i不是约数跳过 
		for(LL j = i; j*j <= n/i; j++) {	//i已经确定,j*j<=n/i 
			if(n/i%j != 0) continue;	//i确定的情况下,j不是约数跳过
			
			LL k = n/i/j;	//计算求得k
            //i,j,k是一组,k一定是约数
			
			if(i==j&&j==k) res++;	//三个数相等加1种方案
			else if(i!=j && j!=k) res+=6;	//三个数不相等加6种方案,P33 = 6 
			else res+=3;	//两个数相等加3种方案 ,P33-(P22-1)*x=x => x=3
		}
	}
	cout << res << endl;
	
	return 0;
}

方法二代码:

/*
2430
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

typedef long long LL;

vector<LL> d;

int main() {
	LL n = 2021041820210418;
	
	//存n的所有约数
	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: 路径

image-20210502234603646

思路:经典最短路问题,用迪杰斯特拉、SPFA、佛洛依德都可以。我这里用迪杰斯特拉算法

答案:10266837

代码:

/*
10266837
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <climits>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL; 

const int N = 2030, INF = 0x3f3f3f3f;
int n;
int e[N][N], dist[N];
bool st[N];

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

int lcm(int a, int b) {
	int c = gcd(a,b);
	return a*b/c;
}

int main() {
	n = 2021;
	//初始化邻接矩阵
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			if(i == j) e[i][j] = 0;
			else if(abs(i-j) <= 21) e[i][j] = lcm(i,j);
			else e[i][j] = INF;
		}
	}
	
	//dist是1到个各节点的初始路程 
	for(int i = 1; i <= n; i++) dist[i] = e[1][i];
	st[1] = true;
	
	for(int i = 1; i <= n - 1; i++) {	//一共需要找n-1次最近的结点 
		int u = -1;	//距离1最近的结点编号
		//找最近 
		for(int j = 1; j <= n; j++)
			if(!st[j] && (u == -1 || dist[j] < dist[u])) 
				u = j;
        
		st[u] = true;	//u已经处理完毕
        
		//通过u更新1到各结点的最近距离 
		for(int v = 1; v <= n; v++)
            dist[v] = min(dist[v], dist[u] + e[u][v]);
	}
	
	cout << dist[n] << endl;
	
	return 0;
}


试题 F: 时间显示

image-20210503001854786

image-20210503001937844

image-20210503002011621

思路:注意要舍去年月日,取时分秒。

代码:

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

typedef long long LL;

int main() {
	LL n;
	cin >> n;
	
	n /= 1000;	//舍去毫秒,即舍去最后3位 
	n %= 86400;	//取时分秒,24*60*60=86,400秒 
	
	int h = n / 3600;	//取小时,一小时60*60=3600秒 
	int m = n % 3600 / 60;	//取分钟
	int s = n % 60;	//取秒 
	
	printf("%02d:%02d:%02d\n",h,m,s);
	
	return 0;
}

试题 G: 砝码称重

image-20210503002042972

image-20210503002053702

image-20210503002105806

思路:背包问题,用DP。比赛的时候用dfs暴力应该只得了部分分。

分析一下,假设物品放右边,Wi是每个砝码的重量,Ci是该砝码当前状态下的取值,那么每个砝码有3种状态,不放(Ci=0),放左边(Ci=Wi),放右边(Ci=-Wi)。

状态表示:f[i][j]表示为 前 i 个砝码中选,重量为 j 的情况是否可以称出来。

状态计算:最后一个砝码有三种情况:不选,取正值,取负值。那么3种情况回溯到 i - 1,有一种为真f[i][j]就为真。

转移方程:f[i][j]= f[i-1][j] | f[i-1][j-w[i]] | f[i-1][j+w[i]]

初始化:f[0][0] = true,啥也不放,重量为0,肯定可以称出来。

DP做法代码:

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

const int N = 105, M = 200010;
int n,m;
int w[N];
bool f[N][M];

int main() {
	cin >> n;
	for(int i = 1; i <= n; i++) scanf("%d",&w[i]), m += w[i];
	
	f[0][0] = true;
	
	for(int i = 1; i <= n; i++) {
		for(int j = 0; j <= m; j++) {
            //如果遇到一个负重量,把它归于它的正重量。
			f[i][j] = f[i-1][j] | f[i-1][abs(j-w[i])] | f[i-1][j+w[i]];
		}
	}
	
	int res = 0;
	for(int j = 1; j <= m; j++)
		if(f[n][j]) res++;
	cout << res << endl;
	
	return 0;
}

DFS暴力做法代码(TLE,过部分样例):

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

int n,res;
int w[100010]; 
bool st[100010];

void dfs(int k,int m) {	//k个的砝码,重量是sum 
    if(k > n) {
        if(m>0 && !st[m]) {
            res++;
            st[m]=true;
        }
        return;
    }
    //还没选够n个砝码 
    dfs(k+1,m);//不选
    dfs(k+1,m+w[k]);//放左边,+wi
    dfs(k+1,m-w[k]);//放右边,-wi
}

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

    dfs(0,0);
    cout << res << endl;
    
    return 0;
}

试题 H: 杨辉三角形

image-20210511114354706

思路:

image-20210511114419886

因为对称,所以只看左半边。

image-20210511114434265

斜着看,枚举每个斜行,因为每个斜行里是单调的,所以可以用二分查找,一个斜行里找不到就换下一个斜行找。

n最大1e9,C(34, 17) > 1e9, C(32, 16) < 1e9,因此只要枚举前16个斜行即可。

设第k个斜行,该斜行的每个元素为C(2k,k),C(2k+1,k),C(2k+2,k)…

当k等于1的时候,cnk一定有解,所以r等于n就行了。
crk,r前面有r行,第1行一个数,第二行2个数,是等差数列,r(1+r)/2,然后k加1

然后注意图上有2个6,更内行的6才是答案,所以行要从16往小遍历。

这题比赛时也只能暴力了。

代码:

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

typedef long long LL;

int n;

//计算组合数,a * (a-1) .. b个 / b!
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;
}

//检查第k行
bool check(int k) {
	LL l = 2 * k, 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;
	// C(r, k)的从0开始的顺序
	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: 双向排序

image-20210511114257925

思路:模拟骗分吧。

代码:

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

const int N = 100010;
int a[N];

int main() {
	int n,m;
	cin >> n >> m;	//长度,操作次数
	for(int i = 1; i <= n; i++) a[i] = i;	//初始化 
	while(m--) {
		//类型p,参数q
		//0,a[1~q]降序	1,a[q~n]升序
		int p,q;
		scanf("%d%d",&p,&q);
		if(p==0) {
			sort(a+1,a+q+1,greater<int>());
		}else {
			sort(a+q,a+n+1);
		}
	}
	for(int i = 1; i <=n ; i++) printf("%d ",a[i]);
	return 0;
}

试题 J: 括号序列

没做。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值