• 2022年第十三届蓝桥杯软件类决赛C/C++大学B组题解


    题目及自测链接


    1. 卡牌

    题意:
    一共 n n n 种卡牌,每种卡牌 a i a_i ai 张。
    如果有 n n n 张卡牌,每张卡牌各有一张,则称为一套牌。
    m m m 张空白牌,可以写上数字 i i i 充当第 i i i 种牌。第 i i i 种牌最多手写 b i b_i bi 张。
    问,最多能凑出多少套牌?
    n ≤ 2 × 1 0 5 ;   a i , b i ≤ n ;   m ≤ n 2 n ≤ 2 × 10^5;\ a_i , b_i ≤ n;\ m ≤ n^2 n2×105; ai,bin; mn2

    思路
    二分答案
    可以看出最后的答案具有单调性,如果小答案满足不了的话大答案一定也满足不了。所以可以二分答案。
    对于当前答案 m i d mid mid,遍历所有种类牌,尽可能的凑够 m i d mid mid
    如果最后所有种类的牌数都至少有 m i d mid mid 个,说明满足;否则不满足。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 200010;
    int n;
    long long m;
    int a[N], b[N];
    
    bool check(int mid)
    {
    	long long sum = 0;
    	for(int i=1;i<=n;i++)
    	{
    		if(a[i] < mid)
    		{
    			int left = mid - a[i];
    			if(left > b[i]) return 0;
    			sum += left;
    		}
    	}
    	if(sum > m) return 0;
    	return 1;
    }
    
    int main(){
    	scanf("%d%lld", &n, &m);
    	for(int i=1;i<=n;i++) scanf("%d", &a[i]);
    	for(int i=1;i<=n;i++) scanf("%d", &b[i]);
    	
    	int l = 1, r = 4*n;
    	while(l<r)
    	{
    		int mid = l+r+1>>1;
    		if(check(mid)) l = mid;
    		else r = mid-1;
    	}
    	printf("%d", l);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    2. 最大数字

    题意
    给定一个正整数 N N N。可以对其任意一位数字执行任意次以下操作:

    1. 将该位数字加 1。如果该位数字已经是 9,加 1 之后变成 0。

    2. 将该位数字减 1。如果该位数字已经是 0,减 1 之后变成 9。

    总共可以执行操作 1 不超过 A A A 次,操作 2 不超过 B B B 次。
    问最大可以将 N N N 变成多少?

    1 ≤ N ≤ 1 0 17 ; 0 ≤ A , B ≤ 100 1 ≤ N ≤ 10^{17}; 0 ≤ A, B ≤ 100 1N1017;0A,B100

    思路
    dfs + 贪心
    因为数据范围很小,一共只有17位,所以可以对每一位判断是进行1操作还是2操作。
    为了使得最后的数尽可能大,只要剩余增加数的操作就增加数,直到变成9;但是对于减少数,只有能够减少为9的时候才用,减不到就不用。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 200010, mod = 1e9+7;
    int T, n, m;
    string s, ans;
    int x, y;
    
    void dfs(int u)
    {
    	if(u == s.size())
    	{
    		if(s > ans) ans = s;
    		return;
    	}
    	
    	char c = s[u];
    	
    	int t = min(x, 9-(c-'0'));
    	s[u] = char(c+t);
    	x -= t;
    	dfs(u+1);
    	x += t;
    	s[u] = c;
    	
    	if(y >= (c-'0')+1)
    	{
    		y -= (c-'0')+1;
    		s[u] = '9';
    		dfs(u+1);
    		y += (c-'0')+1;
    		s[u] = c;
    	}
    	
    	dfs(u+1);
    }
    
    signed main(){
    	cin >> s >> x >> y;
    	
    	dfs(0);
    	
    	cout << ans;
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    赛场上错的地方在于,加操作也只有至少 9-x 时才用,然后最后剩下了再补。
    但是可能存在这样的情况,在第 i 位时不够 9-s[i],但是在第 i+1 位时够 9-s[i+1],这样就用到了第 i+1 位上,把这一位变成了9,但实际上将 i 位加上一个数贡献更大。
    所以这种做法就是错误的。(对拍真有用啊


    3. 出差

    题意
    一共 N N N 个城市,编号 1   N 1~N 1 N,每个城市有隔离时间 C i C_i Ci
    给出 M M M 条双向路线 u − v u-v uv,每条路线有通行时间 c c c
    问,从城市 1 到达城市 N N N,最少需要多少时间?(终点城市到达即可,不用算上隔离时间)
    1 ≤ N ≤ 1000 , 1 ≤ M ≤ 10000 , 1 ≤ C i ≤ 200 , 1 ≤ u , v ≤ N , 1 ≤ c ≤ 1000 1 ≤ N ≤ 1000 , 1 ≤ M ≤ 10000, 1 ≤ Ci ≤ 200, 1 ≤ u, v ≤ N, 1 ≤ c ≤ 1000 1N1000,1M10000,1Ci200,1u,vN,1c1000

    思路
    最短路
    从节点 x 到节点 tx 需要的时间为:道路通行时间 + tx 的隔离时间

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    #define PII pair<int, int>
    
    const int N = 10010;
    int n, m;
    int a[N];
    vector<pair<int,int> > e[N];
    int dist[N], f[N];
    
    void dij()
    {
    	priority_queue<PII, vector<PII>, greater<PII> > que;
    	que.push(make_pair(0, 1));
    	for(int i=1;i<=n;i++) dist[i] = 0x3f3f3f3f;
    	dist[1] = 0;
    	
    	while(que.size())
    	{
    		int x = que.top().second;
    		que.pop();
    		
    		if(f[x]) continue;
    		f[x] = 1;
    		
    		for(int i=0;i<e[x].size();i++)
    		{
    			PII it = e[x][i];
    			int tx = it.first, dis = it.second;
    			
    			int t = 0;
    			if(tx != n) t = a[tx];
    			
    			if(dist[tx] > dist[x] + dis + t){
    				dist[tx] = dist[x] + dis + t;
    				que.push(make_pair(dist[tx], tx));
    			}
    		}
    	}
    }
    
    int main(){
    	scanf("%d%d", &n, &m);
    	for(int i=1;i<=n;i++) scanf("%d", &a[i]);
    	
    	while(m--){
    		int x, y, z;scanf("%d%d%d", &x, &y, &z);
    		e[x].push_back(make_pair(y, z));
    		e[y].push_back(make_pair(x, z));
    	}
    	
    	dij();
    	
    	printf("%d", dist[n]);
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    场上不知道抽了哪根筋了,把 0x3f3f3f3f 记成了 0x3f,g!


    4. 费用报销

    题意
    给定 n n n 张票据的时间 d a y day day 和金额 v a l val val
    现需要选出若干张票据,要求其中任意两张的时间差不小于 K K K 天,总金额不超过 M M M
    问,选出的票据总金额最大为多少?

    思路
    01背包变式
    把经典01背包中的体积和价值都看作金额,那么这题就是:
    n n n 个物品中选,求总体积不超过 M M M 的条件下能够得到的最大价值。
    定义状态:f[i, j] 表示从前 i 个物品中选,总金额不超过 j 的条件下得到的最大金额。

    不同的是在更新时,需要保证当前物品要从时间差不小于 K K K 天的物品转移。
    将所有物品按时间从小到大排序。
    可以看出,从前到后遍历所有物品时,对于当前物品往前对应的转移物品的位置时不断往后走的,所以可以用一个指针指向其位置,能往后走时就往后走。这样便可以省掉遍历更新物品的这重循环。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 10010;
    int n, m, k;
    int mon[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    int sum[N];
    int f[1010][5010];
    
    struct node{
    	int day, w;
    }a[N];
    
    bool cmp(node a, node b){
    	return a.day < b.day;
    }
    
    int main(){
    	cin >> n >> m >> k;
    	
    	for(int i=1;i<=12;i++) sum[i] = sum[i-1]+mon[i];
    	
    	for(int i=1;i<=n;i++)
    	{
    		int month, day, w;
    		cin >> month >> day >> w;
    		a[i].day = sum[month-1] + day;
    		a[i].w = w;
    	}
    	
    	sort(a+1, a+n+1, cmp);
    	
    	int t = 0;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++)
    		{
    			while(a[i].day - a[t+1].day >= k && t+1 < i) t++;
    			
    			f[i][j] = f[i-1][j];
    			if(j>=a[i].w)
    				f[i][j] = max(f[i][j], f[t][j-a[i].w] + a[i].w);
    		}
    	}
    	cout << f[n][m];
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    赛场上错的地方在于,f[i][j] = f[i-1][j],而不是 f[t][j]
    因为状态定义的是前i个物品中的最大值,所以 f[i, j] 需要初始化为 f[i-1, j] 。后面转移的时候才用到 t


    7. 齿轮

    题意
    给定一个长度为 n n n 的数组 a [ ] a[] a[]
    q q q 次询问,每次询问给出一个数 x x x,判断数组中是否存在两个数使得两数之商为 x x x
    n , a i , q , x < 2 e 5. n, a_i, q, x < 2e5. n,ai,q,x<2e5.

    思路
    埃式筛
    数据范围很大,询问的次数很多,所以基本上是要预处理出来答案。
    转换一下思维,不去遍历两个位置求商,而是遍历数组出现的数 i,然后遍历其所有倍数 j,如果倍数 j 在数组中出现过,那么把 j/i 标记下来。 O ( n l o g n ) O(nlogn) O(nlogn)
    最后 O ( 1 ) O(1) O(1) 处理询问。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 200010, mod = 1e9+7;
    int T, n, m;
    int a[N];
    int f[N];
    bool ans[N];
    
    void init()
    {
    	for(int i=1;i<=2e5;i++)
    	{
    		if(!f[i]) continue;
    		for(int j=i;j<=2e5;j+=i)
    		{
    			if(f[j]) ans[j/i] = 1;
    		}
    	}
    }
    
    signed main(){
    	cin >> n >> m;
    	for(int i=1;i<=n;i++) cin >> a[i], f[a[i]] = 1;
    	
    	init();
    	
    	while(m--)
    	{
    		int x;cin >> x;
    		if(ans[x]) cout << "YES\n";
    		else cout << "NO\n";
    	}
    	
    	return 0; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    场上也看样例猜到了这种题意转换,但是没想到正解,暴力写的,对于每次询问都遍历一遍,判断乘积是否出现过。但是乘积的大小最大到4e10,然后直接搞到数组中re了。。


    8. 搬砖

    题意
    给定 n n n 块砖,对于第 i i i 块砖有重量 w i w_i wi,价值 v i v_i vi
    现在要选出若干块砖,按照一定顺序摞成一竖条。
    要满足:对于其中的每块砖,其上面所有砖重量之和不能超过其自身价值。
    问,这样选出的砖总价值最大为多少?

    思路
    贪心 + 01背包
    很像之前做过的一道贪心题,耍杂技的牛

    定义状态 f[i, j] 表示,从前 i 个物品中选,总重量不超过 j 的条件下,能够得到的最大总价值。

    起初这道题是直接按照价值从小到大排序,然后跑01背包,更新的时候注意满足总价值 k 减去当前物品体积不超过当前物品价值:k - a[i].wht <= a[i].val
    但是不对,需要将所有物品贪心的按照 重量+价值 从小到大排序后再跑。不知道为什么。。(有知道的大佬评论一下~

    Code

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 1010, mod = 1e9+7;
    int T, n, m;
    struct node{
    	int wht, val;
    }a[N];
    int f[N][20*N];
    
    bool cmp(node a, node b){
    	return a.val + a.wht < b.val + b.wht;
    }
    
    signed main(){
    	cin >> n;
    	for(int i=1;i<=n;i++) cin >> a[i].wht >> a[i].val, m += a[i].wht;
    	
    	sort(a+1, a+n+1, cmp);
    
    	int ans = 0;
    	for(int i=1;i<=n;i++)
    	{
    		for(int k=1;k<=m;k++)
    		{
    			f[i][k] = max(f[i][k], f[i-1][k]);
    			if(k >= a[i].wht && k - a[i].wht <= a[i].val)
    				f[i][k] = max(f[i][k], f[i-1][k-a[i].wht] + a[i].val);
    			ans = max(ans, f[i][k]);
    		}
    	}
    	cout << ans;
    	
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    场上最后没时间了,直接暴力搞的,但是暴力的思路都错了。。


    这次打的太糟了,很多不该犯的错误都出来了,赛前没有花时间准备,手感和敏感都下降了,甚至连板子都忘了。。
    以后像这样的比赛前,一定提前做好准备,考前至少模拟一套真题!
    哎,长记性了。

  • 相关阅读:
    CDN是什么,能起到什么作用
    php 遍历PHP数组的7种方式
    SpringMvc学习笔记
    用手势识别来测试视力?试试用百度AI来实现想法
    OGG|Oracle 数据迁移后比对一致性
    华为OD机考算法题:支持优先级的队列
    CF1120 D. Power Tree 巧妙的图论转化
    Studying-代码随想录训练营day15| 222.完全二叉树的节点个数、110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和
    算法-买卖股票的最佳时机II
    ECU安全访问系列_2(代码篇)
  • 原文地址:https://blog.csdn.net/Mr_dimple/article/details/125511965