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;
}

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;
}

赛场上错的地方在于,加操作也只有至少 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;
}

场上不知道抽了哪根筋了,把 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 天的物品转移。
将所有物品按时间从小到大排序。
因为状态定义的是从前 i 个物品中选,所以位置越靠后答案越优,所以每次找最后一个满足时间差不小于 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;
}

赛场上错的地方在于,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; 
}

场上也看样例猜到了这种题意转换,但是没想到正解,暴力写的,对于每次询问都遍历一遍,判断乘积是否出现过。但是乘积的大小最大到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;
}

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


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

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值