2020年第十一届蓝桥杯省赛 第一场(7月5日)B组个人题解

PDF文件下载:

https://files.cnblogs.com/files/Angel-Demon/CB.zip

试题 A: 跑步训练

【问题描述】
小明要做一个跑步训练。
初始时,小明充满体力,体力值计为 (10000) 。如果小明跑步,每分钟损耗 (600) 的体力。如果小明休息,每分钟增加 (300) 的体力。体力的损耗和增加都是均匀变化的。
小明打算跑一分钟、休息一分钟、再跑一分钟、再休息一分钟……如此循环。如果某个时刻小明的体力到达 (0) ,他就停止锻炼。
请问小明在多久后停止锻炼。为了使答案为整数,请以秒为单位输出答案。答案中只填写数,不填写单位。

【答案提交】

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个 整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:3880

#include<bits/stdc++.h>
using namespace std;

void solve(int n) {
	int m = 0;
	while (1) {
		for (int i = 1; i <= 60; ++i) {
			n -= 10,m++;
			if (n == 0) { cout << m; return; }
		}
		for (int i = 1; i <= 60; ++i)
			m++, n += 5;
	}
}

int main() {
	freopen("in.txt", "r", stdin);
	ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	solve(10000);
}

试题 B: 纪念日

答案:52038720

利用计算器现在的日期计算即可

编程也能做,2019年之前只需要判断是否润年再加365/366天。在2020年可以手写计算时间

追求便利就选择了使用自带的日期计算

试题 C: 合并检测

【问题描述】 新冠疫情由新冠病毒引起,最近在 A 国蔓延,为了尽快控制疫情,A 国准 备给大量民众进病毒核酸检测。

然而,用于检测的试剂盒紧缺。

为了解决这一困难,科学家想了一个办法:合并检测。即将从多个人(k 个)采集的标本放到同一个试剂盒中进行检测。如果结果为阴性,则说明这 k 个人都是阴性,用一个试剂盒完成了 k 个人的检测。如果结果为阳性,则说明 至少有一个人为阳性,需要将这 k 个人的样本全部重新独立检测(从理论上看, 如果检测前 k − 1 个人都是阴性可以推断出第 k 个人是阳性,但是在实际操作中 不会利用此推断,而是将 k 个人独立检测),加上最开始的合并检测,一共使用 了 k + 1 个试剂盒完成了 k 个人的检测。

A 国估计被测的民众的感染率大概是 1%,呈均匀分布。请问 k 取多少能 最节省试剂盒?

【答案提交】

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个 整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:10

\(\lceil\frac{100}{k}\rceil + k \ge \sqrt{100} = 10\)

\(即当 100 / k = k 的时候取最小值 等价于 k = 10\)

试题 D: REPEAT 程序

附件 prog.txt 中是一个用某种语言写的程序。

prog.txt 下载地址:Here

其中 REPEAT k 表示一个次数为 k 的循环。循环控制的范围由缩进表达,从次行开始连续的缩进比该行多的(前面的空白更长的)为循环包含的内容。
例如如下片段:

REPEAT 2:
 A = A + 4
 REPEAT 5:
  REPEAT 6:
   A = A + 5
  A = A + 7
 A = A + 8
A = A + 9

该片段中从 A = A + 4 所在的行到 A = A + 8 所在的行都在第一行的循环两次中。

REPEAT 6: 所在的行到 A = A + 7 所在的行都在 REPEAT 5: 循环中。

A = A + 5 实际总共的循环次数是 2 × 5 × 6 = 60 次。

请问该程序执行完毕之后,A 的值是多少?

【答案提交】

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个 整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:241830

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 10;

int w[10];
int main() {
    fstream fs("D:\\Coding\\Cpp\\LanQianCup\\2020B1\\LQC-2020B1-04.txt");
    string s;
    int A = 0;
    while (getline(fs, s)) {
        int len = s.size();
        int id = (len - 5) / 4;
        if (s[len - 1] == ':') w[id] = s[len - 2] - '0';
        else {
            int time = 1;
            for (int i = 1; i < id; i++) time *= w[i];
            A += time * (s[len - 1] - '0');
        }
    }
    cout << A << "\n";
}

试题 E: 矩阵

【问题描述】

把 1 ∼ 2020 放在 2 × 1010 的矩阵里。要求同一行中右边的比左边大,同一 列中下边的比上边的大。一共有多少种方案?

答案很大,你只需要给出方案数除以 2020 的余数即可。

【答案提交】

这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个 整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:1340

我们从 2020 个数里选 1010 个放入第一行,那么为了满足同一行中右边的比左边大,只能升序排列。同理剩下的 1010 个放入第二行的也要升序排列,那么只要对于任意 i∈[1,1010] 都满足第二行第 i 个大于第一行第 i 个就是一种合法方案。从前往后枚举,用dp[i][j] 表示当前枚举了 i 个数,其中 j 个放入第一行的合法方案数

#include<bits/stdc++.h>
using namespace std;
const int N = 2020, mod = 2020;
long long dp[N][N];
int main() {
    //freopen("in.txt", "r", stdin);
    ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    long long cnt = 0;
    dp[1][1] = 1;// 1必然放在第一行
    for (int i = 1; i <= 2020; ++i) {
        for (int j = 1; j <= i; ++j) {
            dp[i][j] += dp[i - 1][j - 1];// 将第i个数放第一行
            if (i - j <= j)
                /*
                    因为是正向枚举,后面的数只会越来越大
                    要随时保持第一行的个数不能比第二行的少
                    否则必然出现这一列第二行比第一行小的情况
                */
                dp[i][j] += dp[i - 1][j];
            dp[i][j] %= mod;
        }
        
    }
    cout << dp[2020][1010];
}

试题 F: 整除序列

【问题描述】

有一个序列,序列的第一个数是 n,后面的每个数是前一个数整除 2,请输 出这个序列中值为正数的项。

【输入格式】

输入一行包含一个整数 n。

【输出格式】

输出一行,包含多个整数,相邻的整数之间用一个空格分隔,表示答案。

【样例输入】

20 

【样例输出】

 20 10 5 2 1

【评测用例规模与约定】 对于 80% 的评测用例,\(1 ≤ n ≤ 10^9\)。 对于所有评测用例,\(1 ≤ n ≤ 10^{18}\)

#include<bits/stdc++.h>
using namespace std;
int main() {
	//freopen("in.txt", "r", stdin);
	ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	long long n; cin >> n;
	for (; n > 0; n >>= 1) {
		cout << n;
		if (n == 1)cout << endl;
		else cout << " ";
	}
}

试题 G: 解码

【问题描述】

小明有一串很长的英文字母,可能包含大写和小写。 在这串字母中,有很多连续的是重复的。小明想了一个办法将这串字母表 达得更短:将连续的几个相同字母写成字母 + 出现次数的形式。 例如,连续的 5 个 a,即 aaaaa,小明可以简写成 a5(也可能简写成 a4a、 aa3a 等)。对于这个例子:HHHellllloo,小明可以简写成 H3el5o2。为了方便表 达,小明不会将连续的超过 9 个相同的字符写成简写的形式。 现在给出简写后的字符串,请帮助小明还原成原来的串。

【输入格式】

输入一行包含一个字符串。

【输出格式】

输出一个字符串,表示还原后的串。

【样例输入】

H3el5o2

【样例输出】

 HHHellllloo

【评测用例规模与约定】

对于所有评测用例,字符串由大小写英文字母和数字组成,长度不超过 100。

请注意原来的串长度可能超过 100。

#include<bits/stdc++.h>
using namespace std;
int main() {
	//freopen("in.txt", "r", stdin);
	ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	string s; cin >> s; int n;
	for (int i = 0; i < s.length(); ++i) {
		char c = s[i];
		if (c >= '0' && c <= '9')continue;
		char d = s[i + 1];
		if (d >= '0' && d <= '9')n = d - '0';
		else n = 1;
		while (n--)cout << c;
	}
}

试题 H: 走方格

【问题描述】

在平面上有一些二维的点阵。 这些点的编号就像二维数组的编号一样,从上到下依次为第 1 至第 n 行, 从左到右依次为第 1 至第 m 列,每一个点可以用行号和列号来表示。 现在有个人站在第 1 行第 1 列,要走到第 n 行第 m 列。只能向右或者向下 走。 注意,如果行号和列数都是偶数,不能走入这一格中。 问有多少种方案。

【输入格式】

输入一行包含两个整数 n, m。

【输出格式】

输出一个整数,表示答案。

【样例输入】

3 4

【样例输出】

 2 

【样例输入】

6 6

【样例输出】

0 

【评测用例规模与约定】

对于所有评测用例,1 ≤ n ≤ 30, 1 ≤ m ≤ 30。

一看到这道题想到了DFS,但测试30,29的时候发现跑了很久,那这样提交肯定超时,改用动态规划吧

//TLE
#include<bits/stdc++.h>
using namespace std;
int cnt, n, m;
void dfs(int x, int y) {
	if (x == n && y == m) { cnt++; return; }
	if(x > n || y > m)return ;
	if (x % 2 == 0 && y % 2 == 0)return;
	dfs(x + 1, y);
	dfs(x, y + 1);
}

int main() {
	freopen("in.txt", "r", stdin);
	ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	if (n % 2 == 0 && m % 2 == 0) { cout << 0; return 0; }
	dfs(1, 1);
	cout << cnt << endl;
}
//DP
#include<bits/stdc++.h>
using namespace std;
long long dp[40][40];
int main() {
	//freopen("in.txt", "r", stdin);
	ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	int n, m; cin >> n >> m;
	if (n % 2 == 0 && m % 2 == 0)cout << 0;//如果终点就是无法走进的点直接输出0
	else {
		for(int i = 1;i <= n;++i)
			for (int j = 1; j <= m; ++j) {
				if (i == 1 && j == 1) {
					dp[i][j] = 1;
					continue;
				}
				if ((i & 1) || (j & 1))
					dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
			}
	}
	cout << dp[n][m];
}

试题 I: 整数拼接

【样例输入】

 4 2 
 1 2 3 4 

【样例输出】

6

\(cnt[i][j]\) 记录在之前的数里,在后面补 i 个 0 对 K 取余结果为 j 的数的个数。 对于某个数 n,累加
\(cnt[len(n)][(K−(n\%K))\%K]\)

分别从前往后跑一遍,从后往前跑一遍并统计就好了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 100;
ll n, m, cnt, a[N], k;
ll dp[11][N];
int get_len(ll x) {
	int ans = 0;
	while (x) {
		++ans;
		x /= 10;
	}
	return ans;
}
ll pow10(int x) {
	int t = 1;
	while (x--)t *= 10;
	return t;
}
int main() {
	freopen("in.txt", "r", stdin);
	ios_base::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> k;
	for (int i = 0; i < n; ++i)cin >> a[i], dp[get_len(a[i])][a[i] % k]++;
	for (int i = 0; i < n; ++i)
		for (int j = 1; j <= 10; ++j) {
			ll t = a[i] % k * pow10(j);
			cnt += dp[j][(k - j % k) % k];
			if (j == get_len(a[i]) && (k - t % k) % k == a[i] % k)cnt--;
		}
	cout << cnt;
}

21.4.12 update:优化 pow10 为 qpow 快速幂

// Murabito-B 21/04/12
#include <bits/stdc++.h>
using namespace std;
using ll    = long long;
const int N = 1e5 + 100;
ll n, m, cnt, a[N], k;
ll dp[11][N];
int get_len(ll x) {
    int ans = 0;
    while (x) ++ans, x /= 10;
    return ans;
}
ll qpow(ll a, ll b) {
    ll ans = 1;
    for (; b; a = a * a, b >>= 1)
        if (b & 1) ans = ans * a;
    return ans;
}
void solve() {
    cin >> n >> k;
    for (int i = 0; i < n; ++i) cin >> a[i], dp[get_len(a[i])][a[i] % k]++;
    for (int i = 0; i < n; ++i)
        for (int j = 1; j <= 10; ++j) {
            ll t = a[i] % k * qpow(10, j);
            cnt += dp[j][(k - j % k) % k];
            if (j == get_len(a[i]) && (k - t % k) % k == a[i] % k) cnt--;
        }
    cout << cnt;
}
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    solve();
    return 0;
}

试题 J: 网络分析

【样例输入】

4 8 
1 1 2 
2 1 10 
2 3 5 
1 4 1 
2 2 2 
1 1 2 
1 2 4 
2 2 1 

【样例输出】

 13 13 5 3

看到这种合并题,第一反应应该就是dsu啊?那我们考虑怎么搞并查集,普通并查集显然8太行(或者维护起来很麻烦),观察性质可以知道,我们只需要维护当前点p及其当前点p的root的差值就可以了。那就好办了啊,带权并查集直接上啊,所以每个点最后的结果就是它与它祖先root的差值+root自身的值就可以了。

我们对于操作1,那就是非连通块的话合并并且更新其d值,已经是连通块就continue掉。

我们对于操作2,其实意思就是在他祖先点进行修改就可以了,因为我们要求的最终结果是祖先自己的值+该点与祖先的差值。

#include<bits/stdc++.h>
#define ll long long
const int maxn = 1e4 + 5;
int fa[maxn], d[maxn], now[maxn], n, m;
int find(int x) {
    if (x != fa[x]) {
        int temp = fa[x];
        fa[x] = find(fa[x]);
        d[x] += d[temp];
    }
    return fa[x];
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) fa[i] = i;
    while (m--) {
        int op; scanf("%d", &op);
        if (op == 1) {
            int a, b; scanf("%d%d", &a, &b);
            int eu = find(a), ev = find(b);
            if (eu != ev) {
                fa[ev] = eu;
                d[ev] = now[ev] - now[eu];
            }
        }
        else {
            int p, t; scanf("%d%d", &p, &t);
            int rt = find(p);
            now[rt] += t;
        }
    }
    for(int i= 1;i <= n;++i) {
        printf("%d ", now[find(i)] + d[i]);
    }
    puts("");
}

总结

从2013年蓝桥杯做到2020年,发现蓝桥杯从以前的ACM赛制逐渐向ACM靠拢。算法类涉及加深了,像7月份这场就两DP+带权并查集。基本算法稍微有点忘记了,开始复习之路2333

posted @ 2020-10-03 10:40  RioTian  阅读(2990)  评论(7编辑  收藏  举报