2023睿抗机器人开发大赛CAIP-编程技能赛-本科组(省赛) 全部题解

RC-u1 亚运奖牌榜

2022 年第 19 届亚运会即将在杭州召开,杭州已经做好准备欢迎全亚洲的观众一同参与亚运盛会了!

你正在开发一款跟亚运奖牌计算相关的 App。给定两个国家的获奖情况,你的任务是计算这两个国家/地区的奖牌情况,并确定哪个国家/地区要排在奖牌榜的前面。

输入格式:

输入第一行是一个正整数 N (1≤N≤1000),表示总共有 N 条获奖记录。

接下来的每一行都是形如以下的一条记录:

Ci​,Pi​

其中 Ci​=0,1,0 表示是第一个国家/地区,1 表示是第二个国家/地区;Pi​=1,2,3,1 表示金牌,2 表示银牌,3 表示铜牌。

输出格式:

首先输出两行,第一行是第一个国家/地区的金牌、银牌、铜牌获得数,用空格隔开;第二行是第二个国家/地区的奖牌获奖情况,要求与格式同第一个国家/地区。

最后一行,如果是第一个国家/地区排在前面,输出 The first win!,否则输出 The second win!

排在前面的定义是:先比较金牌数,金牌数较大的排在前面;如金牌数相等,比较银牌数,银牌数较大的在前面;如金牌银牌数都相等,则比较铜牌数,铜牌数较大的在前面。

保证数据不存在奖牌数完全相同的情况。

输入样例:

15
0 1
0 2
0 3
0 1
0 1
0 2
0 3
1 3
1 3
1 3
1 3
1 2
1 1
1 1
1 1

输出样例:

3 2 2
3 1 4
The first win!

题解: 

挨个比较就行, 注意不要看错题目
#include<bits/stdc++.h>
using namespace std;

struct info{
	int x[4] = {0};
	int p;
	bool operator < (const info &X) const{
		if(x[1] != X.x[1])	return x[1] < X.x[1];
		if(x[2] != X.x[2])	return x[2] < X.x[2];
		return x[3] < X.x[3];
	}
}arr[3];

int main() {
	int n;
	cin >> n;
	arr[1].p = 1;
	for(int i = 1; i <= n; ++ i) {
		int p, x;
		cin >> p >> x;
		arr[p].x[x] ++;
	}
	for(int i = 0; i < 2; ++ i) {
		for(int j = 1; j <= 3; ++ j) {
			cout << arr[i].x[j];
			if(j != 3)	cout << " ";
		}
		cout << '\n';
	}
	
	sort(arr, arr + 2);
	
	if(arr[0].p == 0)	cout << "The second win!";
	else	cout << "The first win!";
	
	
	return 0;
}

RC-u2 出院

分数 15

A:最近出了一个饮料营养等级你们知道吗?例如无糖的饮料是 A 级,可乐是 D 级……
B:那……无糖可乐是什么级别?
C:AD 级吧。
A:出院!
B:出什么院,你也给我进去!

以上是某群中一段有趣的对话。请你按照里面的逻辑,在已知某些饮料的等级的情况下,给饮料定级。定级的方法是:

  • 如果是已知等级的饮料,直接输出等级;
  • 对于一个新饮料的名字,你需要将名字拆成两个已知等级的部分,然后输出这个级别。例如:Diet是A,Coke是D,那么DietCoke就是AD;
  • 如果新饮料无法拆解或者有多种拆解方法,统一定为 D 级。

输入格式:

输入第一行是两个正整数 N,M (1≤N,M≤100),表示已知的饮料有 N 种,需要定级的饮料有 M 种。

接下来首先是 N 行,每行是一个字符串和一个字符,表示一种饮料的名字和对应的等级,等级只有 A,B,C,D 四种。

然后是 M 行,每行是一个字符串,表示需要定级的饮料的名字。

所有饮料名字只包含有大小写字母,长度不超过 30,给定拥有等级的饮料的名字不会重复。

输出格式:

对于每一个需要定级的饮料,输出定好的定级。

输入样例:

5 6
Diet A
LowSugarTea B
Milk C
Coke D
Water A
DietCoke
Pepsi
Milk
CokeWater
GoodMilk
dietCoke

输出样例:

AD
D
C
DA
D
D

题解:

看清题意, 只能分解成两个串

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


int main() {
	int n, m;
	cin >> n >> m;
	map<string, string> mp;
	for(int i = 1;i <= n; ++ i) {
		string s1, s2;
		cin >> s1 >> s2;
		mp[s1] = s2;
	}
	
	while(m --) {
		string s;
		cin >> s;
		if(mp.count(s)) {
			cout << mp[s] << '\n';
			continue;
		}
		int cnt = 0;
		string ans = "";
		for(int i = 1; i < (int)s.size(); ++ i) {
			string s1 = s.substr(0, i), s2 = s.substr(i);
			if(mp.count(s1) && mp.count(s2)) {
				++ cnt;
				ans = mp[s1] + mp[s2];
			}
		}
		if(ans.empty() || cnt > 1)	cout << "D\n";
		else	cout << ans << '\n';
	}
	
	return 0;
}

RC-u3 骰子游戏

分数 20

在某个游戏中有一个骰子游戏。在游戏中,你需要投掷 5 个标准六面骰子(骰子为一个正方体,6 个面上分别有1、2、3、4、5、6中的一个数字,骰子的质量均匀),投出的点数根据组合会获得一个“获胜等级”。获胜等级从高到低如下:

  • 五个同点数 - 五个骰子显示相同的点数
  • 四个同点数 - 四个骰子显示相同的点数
  • 葫芦 - 一对和一个三个同点数(如1、1、3、3、3)
  • 六高顺子 - 投出的点数为 2、3、4、5、6
  • 五高顺子 - 投出的点数为 1、2、3、4、5
  • 三个同点数 - 三个骰子显示相同的点数(如1、1、1、2、3)
  • 两对 - 投出的点数中有两对是相同的(如 1、1、2、2、3)
  • 一对 - 投出的点数有一对是相同的(如 1、1、2、3、4)
  • 无 - 除去以上的其他情况

给定你已经投出的一次结果,现在假设你可以选择任意个骰子重投一次,请问怎么样操作,才能最大化在重骰后获得更好的获胜等级的概率呢?

注意:更好的获胜等级需要严格地比当前的获胜等级更好,例如 1、1、2、2、3 如果重骰后变为 1、1、3、3、4 并不比当前的获胜等级更好。

输入格式:

输入第一行是一个正整数 T (1≤T≤10),表示接下来有多少组数据。
每组数据只有一行 5 个数字,表示第一次投出的 5 个骰子的点数。

输出格式:

对于每组数据输出三个整数,其中第一个整数为为了获得最大的概率需要重新骰几个骰子,后面的两个整数为重骰骰子后概率的最简分数,其中第二个整数为分子,第三个整数为分母。如果分子为 0,分母为 1。

如果有多种获得最大概率的情况,取重骰的骰子数最少的方案。

输入样例:

3
1 1 2 2 3
1 1 2 3 4
1 1 1 2 3

输出样例:

3 4 9
3 13 18
2 4 9

样例说明:

样例的第一组数据中,一种方案是:重骰最后三个骰子以获得最大的概率(只要重骰的有一个“1”或者三个均相等即可)。

题解:

        模拟题, 先从小到大枚举重置的筛子个数, 再枚举重置哪些筛子, 对每一种情况计算一个成功的概率, 为方便判断概率大小可以增加筛子数时给已有答案分子分母同时 x6 进行通分, 判断概率大小就只用比较分子了. 最后记录答案并输出, 注意特判最高级的得分, 因为无法筛出更高级的得分.

#include<bits/stdc++.h>
using namespace std;
int arr[6], b[6], n = 5;

struct info {
	int x, y, cnt;
};

info ans;	//答案, x为分子, y为分母, cnt为重置多少个筛子
int ct, p;	//ct 为每种情况的成功次数, p为初始序列的得分
vector<int> v;	//重置哪些位置上的筛子

int check(int a[]) {	//判断等级的函数
	map<int,int> mp;
	for(int i = 1; i <= n; ++ i) {
		mp[a[i]] ++;
	}
	if((int)mp.size() == 1)	return 1;
	
	if((int)mp.size() == 2) {
		for(auto &[x, y] : mp) {
			if(y == 4) {
				return 2;
			}
			if(y == 3) {
				return 3;
			}
		}
		
	}
	
	if((int)mp.size() == 5 && !mp.count(1))	return 4;
	if((int)mp.size() == 5 && !mp.count(6))	return 5;
	
	for(auto &[x, y] : mp) {
		if(y == 3) {
			return 6;
		}
	}
	
	int cnt = 0;
	for(auto &[x, y] : mp) {
		if(y == 2) {
			++ cnt;
		}
	}
	
	if(cnt == 2)	return 7;
	if(cnt == 1)	return 8;
	return 9;
	
}

void dfs(int now) {
	if(now == (int)v.size()) {
		if(check(b)< p)	++ ct;	//成功
		return;
	}
	
	for(int i = 1; i <= 6; ++ i) {	//枚举位置上的所有数字(情况)
		b[v[now]] = i;
		dfs(now + 1);
	}
	
}

void solve() {
	for(int i = 1; i <= n; ++ i) {
		cin >> arr[i];
	}
	ans = {0, 0, 0};	//多组测试数据, 记得清空答案
	p = check(arr);	// 得到初始序列的得分
	if(p == 1) {	//得分最高级, 直接输出
		cout << 0 << " " << 0 << " " << 1 << '\n';
		return;
	}
	int q = 1;	//概率的分母
	for(int k = 1; k <= 5; ++ k) {	// 重摇k个筛子
		q *= 6;	//每次多重置一个筛子, 分母 * 6
		ans.x *= 6, ans.y *= 6;	// 给答案通分
		
		for(int i = 0; i < 1 << n; ++ i) {	//状态压缩, 在二进制情况从右往左数第i位为1则表示重置第i位筛子
			if(__builtin_popcount(i) != k)	continue;	// 二进制数的1不为k, 即重置的筛子数量不等于k
			v.clear();	//清空上次的内容
			ct = 0;	//重置成功次数
			for(int j = 1; j <= 5; ++ j)	b[j] = arr[j];	//重置b数组
			
			for(int j = 0; j < 5; ++ j) {
				if(i >> j & 1) {	//拿到重置筛子的位置
					v.push_back(j + 1);
				}	
			}
			
			
			dfs(0);
			
			if(ans.y == 0)	ans = {ct, q, k};	//第一次直接记录答案
			else {
				if(ct > ans.x)	{	//比答案更优 记录
					ans = {ct, q, k};
				}		
			}
					
		}
	}
	
	int ttt = __gcd(ans.x, ans.y);//题目要求最简分数

	
	cout << ans.cnt << " " << ans.x / ttt << " " << ans.y / ttt << '\n';
	
	
}

int main() {
	
	int t;
	cin >> t;
	while(t --) {
		solve();
	}
	return 0;
}

RC-u4 相对论大师

分数 25

在某个直播间里,观众常常会发送类似这样的弹幕:

鱼越大,鱼刺越大;鱼刺越大,肉越少;肉越少,鱼越小;所以鱼越大,鱼越小

这样通过一连串推导得出一个搞笑的结论的弹幕发送者被称为“相对论大师”。

现在给定一系列已有的推论,请你从给定的推论中挑选一些,组成一条类似于上面的弹幕,成为一名“相对论大师”。

输入格式:

输入第一行是一个正整数 N (1≤N≤1000),表示总共有多少条推论。

接下来的 N 行,每行有两对四个元素,形如下:

A 0 B 1
每对元素表示一个论点:第一个是一个长度不大于 5 的、只包含大小写字母的字符串,称为论点的核心;第二个数字固定为 0 或者 1,代表论点核心的方向属性。为简单理解,你可以将 0 理解为正面方向,1 理解为负面方向。例如:

YuCi 0 Rou 1

就可以理解为鱼刺大,肉少 。

于是一行中的两个论点就形成一条推论,表示第一个核心某个方向的属性能推出第二个核心的某个方向的属性,即鱼刺越大,肉越少

输出格式:

按照弹幕格式输出一行,例如:

Yu 0 YuCi 0 YuCi 0 Rou 1 Rou 1 Yu 1 = Yu 0 Yu 1

具体格式要求为:在一行中输出从起始论点到最终论点的所有推论,论点格式与输入相同,论点间以1个空格分隔。随后输出等号(等号前后均有1个空格),最后是相互矛盾的起始和终止论点。

如果有多种方案,选择使用推论最少的;推论条数相同的输出任意一种方案均可。

在方案中每条推论仅可使用一次。保证有解,且给定的推论中没有相同的推论。

输入样例:

5
Yu 0 Yuci 0
Rou 1 Yu 1
Yuci 0 Rou 1
Yuci 0 Gutou 0
Gutou 0 Rou 0

输出样例:

Yu 0 Yuci 0 Yuci 0 Rou 1 Rou 1 Yu 1 = Yu 0 Yu 1

提示:

本题返回结果若为格式错误均可视为答案错误

题解:

题意一言难尽, 只要推论的起点和终点恰好相反就行, 不一定是鱼越大 鱼越小

直接将一条推论分为两部分, 分别看作起点和终点, 进行哈希后建图, 再枚举每个点为终点跑bfs,同时记录答案的长度, 若有更短的路径, 则更新答案. 记录路径则只需开个pre数组记录从哪里来的就行最后输出答案, 注意格式

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

map<string,int> mp;
map<int, string> mp2;

const int N = 1e3 + 10;
int pre[N];
bool st[N];
int h[N], e[N], ne[N], idx;

void add(int u, int v) {
	e[idx] = v, ne[idx] = h[u], h[u] = idx ++;
}

vector<string> res;	//	记录答案

void bfs(string T1) {
	string T2 = T1;
	if(T2.back() == '0')	T2.back() = '1';
	else	T2.back() = '0';

	int start = mp[T1], end = mp[T2];
	memset(pre, -1, sizeof pre);
	memset(st, false, sizeof st);
	queue<int> q;
	q.push(start);
	st[start] = true;
	
	while(!q.empty()) {
		auto t = q.front();
		q.pop();
		for(int i = h[t]; ~i; i = ne[i]) {
			int j = e[i];
			if(st[j])	continue;
			st[j] = true;
			q.push(j);
			pre[j] = t;
		}
	}
	
	if(pre[end] == -1)	return;	//未跑到起点, 无解
	
	vector<string> ans;
	
	while(pre[end] != 0) {	//记录路径
		ans.push_back(mp2[end]);
		end = pre[end];
	}
	
	
	if(res.empty() || (int)res.size() > (int)ans.size())	//此路更优, 替换答案
		res = ans;
	
	
}

int main() {
	int n, cnt = 0;
	cin >> n;
	memset(h, -1, sizeof h);
	for(int i = 1; i <= n; ++ i) {
		string s1, s2, t1 ,t2;
		cin >> s1 >> t1 >> s2 >> t2;
		s1 += t1, s2 += t2;
		if(!mp.count(s1))	mp[s1] = ++ cnt, mp2[cnt] = s1;	//	哈希
		if(!mp.count(s2))	mp[s2] = ++ cnt, mp2[cnt] = s2;	// 双向哈希
		add(mp[s2], mp[s1]);
	}
	
	for(auto &[x, y] : mp) {	// 枚举终点
		bfs(x);
	}
	
	for(int i = 0; i < (int)res.size(); i ++) {	//输出答案
		string t1, t2;
		for(int j = 0; j < (int)res[i].size() - 1; ++ j)	t1 += res[i][j];
		t2 += res[i].back();
		cout << t1 << " " << t2 << ' ';
		if(i != 0 )	{
			if(i == (int)res.size() - 1) {
				cout << "= ";
				for(int j = 0; j < res[0].size() - 1; ++ j) {
					cout << res[0][j];
				}
				cout << " " << res[0].back() << " " << t1 << " " << t2 << '\n';
			} 
			else	cout << t1 << " " << t2 << ' ';
		}
	}
	
	
	
	return 0;
}

RC-u5 相对成功与相对失败

分数 30

注意:题面内容表达纯属娱乐,与现实无关!

网上常有人说:看 XX 只能度过一个相对成功/失败的人生。不妨假设把这个句式套用在“参加睿抗比赛“以及“玩手机游戏”上,那么有:

  • “参加睿抗比赛”必然比“不参加睿抗比赛”要成功;
  • “玩手机游戏“必然比“不玩手机游戏”要失败。

现在有 N 个人,已知这些人自己填写的是否参加了睿抗比赛以及是否玩手机游戏的情况,以及他们实际上的成功程度的排序顺序,请问最少有多少人在填写情况时说谎了?

输入格式:

输出第一行为一个正整数 T (1≤T≤5),表示数据组数。

每组数据第一行是一个正整数 N (1≤N≤105),表示总共的人数。

接下来的 N 行,第 i 行有两个数字 Ai​,Bi​,表示第 i 位参赛选手是否参加了睿抗比赛以及是否玩手机游戏,0 为没有参加/没有玩,1 为参加了/玩了。

最后一行有 N 个数,为一个选手编号 1 到 N 的排列,表示选手成功程度的排序。排序顺序从最成功到最失败。

选手编号从 1 开始。

输出格式:

对于每组数据,输出一个整数,表示最少的说谎人数。

输入样例:

3
5
1 0
1 0
0 0
0 0
0 1
1 2 3 4 5
5
1 0
1 0
0 0
0 0
0 1
5 4 3 2 1
5
1 0
0 1
0 0
0 1
1 1
4 2 1 3 5

输出样例:

0
3
2

样例说明:

对于样例中的第三组数据,一种可能是编号为 4 的选手和编号为 2 的选手说谎了。

题解:

坑点: 00 == 11 , 不知道坑点就....(害!)

 根据题目给的数据和排序规则, 自己排一遍序,得到一个新的序列(若没人说慌, 这就是真正的位置排序), 依此序列(得分多少)将位置哈希成实际排名,  再将题目给的序列转为实际排名,题目就转为求最长不下降子序列, 这题就完美解决了

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
struct info {
	int x, y, id;
	
	bool operator < (const info &X) const {
		if(X.x != x)	return x > X.x;
		return y < X.y;
	}
	
}arr[N];

int b[N], q[N];
int n;


int get() {
	int rr = 0;
	for(int i = 1; i <= n; ++ i) {
		if(rr == 0 || b[i] >= q[rr])	{
			q[++ rr] = b[i];
			continue;
		}
		int l = 1, r = rr;
		while(r > l) {
			int mid = l + r >> 1;
			if(q[mid] <= b[i])	l = mid + 1;
			else	r = mid;
		}
		q[l] = b[i];
	}
	return rr;
}

void solve() {
	
	cin >> n;
	for(int i = 1; i <= n; ++ i)	cin >> arr[i].x >> arr[i].y, arr[i].id = i;
	sort(arr + 1, arr + 1 + n);
	for(int i = 1; i <= n; ++ i)	cin >> b[i];
	
	int cnt = 0;
	arr[0].x = -1, arr[0].y = arr[0].y;
	map<int,int> mp;
	for(int i = 1; i <= n; ++ i) {
		if(arr[i].x == arr[i-1].x && arr[i].y == arr[i-1].y)	mp[arr[i].id] = mp[arr[i-1].id];
		else if(arr[i].x == arr[i].y && arr[i].x != arr[i-1].x && arr[i-1].x == arr[i-1].y)	// 00 == 11的坑点
			mp[arr[i].id] = mp[arr[i-1].id];
		else	mp[arr[i].id] = ++ cnt;
	}
	
	for(int i = 1; i <= n; ++ i)	b[i] = mp[b[i]];

	cout << n - get();
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int t;
	cin >> t;
	while(t --) {
		solve();
	}
	
	return 0;
}

  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值