每日刷题——0630——2023睿抗本科组省赛第三题

首先先来看看题目,这次我真的一点思路也没有了,晕(痛苦),题目如下:

输入样例: 

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.投掷五个骰子,一共会有九种情况

2.可以对任意个骰子进行重投

3.需要得到最大的概率去获得更好的获胜成绩就行了

4.既然要更好的获胜情况,那么肯定是得先知道现在的获胜等级是多少的,然后再去分析变动哪几个骰子去获得更好的更好的获胜情况

5.目前想到的感觉只有遍历?遍历每一种情况分析所得结果

对样例的分析:

1. 第一个样例:1 1 2 2 3  是属于“两对”这个等级

代码实现步骤:

1.一步一步来吧,心平气和,加油!!!

2.首先得判断当前投掷出来的是什么等级 ---->单独写一个函数

函数分析:

一、判断投掷出来的是什么获胜等级函数分析:
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;
	
}

1.如果只利用数组,无法判断出来究竟有多少个不同的点数,而且如果能判断的话,也是非常麻烦

我的思路是:1,2,3,4,5,6设置六个长度的数组,如果投骰子的点数是这其中某一个点数,那么就让这个位置的数字加一,设置五个变量,分别接受每一个数组格子里边的个数,然后再判断每一个变量的大小,但是变量的变化情况实在是太多了,如果用if判断语句就要写六重,而且还要用一个变量来接受究竟有几对满足获胜等级的条件,而且判断出来之后也还要再用if语句,所以非常麻烦

上边是数组的麻烦情况,所以避重就轻,我们利用以数组为原型的其他容器map来写这道题目,毕竟其他容器有很多的方法可以直接利用,缓解了麻烦

2.利用map和数组结合,先把投掷的骰子点数暂时存在数组中,以后也便于利用这些数字,然后map的键是唯一的,map的size大小代表有几个不同的点数,然后每个map的值代表的这个键有多少个

3. size和map中的值分别来确定它的获胜等级

第一种  五个同点数-->size数为1

第二种  四个同点数,葫芦-->都是size数为2,但是四个同点数是有map中的值为4,葫芦是map中有的值为3

第三种  六高顺子,五高顺子,都是固定的,size数均为5,但是六高顺子里边没有1,五高顺子里边没有6

第四种  三个同点数和两对---->size数为3,但是三个同点数有map中的键值为3,两对里边有map中的键值为2

第五种  一对---->size数为4

第六种   除了其他的都是第六种

二、DFS算法:

1.代码如下:

 //DFS算法 
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);
	}
	
}

2.学习理解DFS

关于DFS深度优先搜索的算法入门,可以看这个链接:DFS算法入门

BFS广度优先搜索的算法入门:BFS算法入门

3.自我理解与总结:

1.DFS底层运用到了栈,属于先进后出,首先,写DFS的出口,到了什么样的情况之后,可以return,然后,对每一个位置上进行重置和标记已经使用过了

2.BFS底层运用到了队列,属于先进先出

三、判断是否解决的函数分析:

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

2.疑问一:结构体里边的大括号,可能是没有学很好当时,所以搜了搜,如下图:                          

大括号也可以对结构体进行初始化

3.代码书写步骤:

1).首先对五个骰子的点数进行输入,输入到数组中去

2).用判断序列的得分来算出此时的初始序列的得分,如果是最高等级,那么就不用进行下面的步骤了,直接对此进行输出,结果为0

3).如果不是最高等级,那么就要对每一个位置上面的骰子点数进行遍历,看能否得到更高的等级,说实话,这一步我没看懂,什么叫状态压缩 这个<<是什么意思?

for(int i = 0; i < 1 << n; ++ i) {    //状态压缩, 在二进制情况从右往左数第i位为1则表示重置第i位筛子

4)知道这里是需要进行骰子点数的遍历,看有没有更好的概率,但是鄙人真的没看懂,不知道怎么具体实现出来的

代码心得:

1.现在真的得改变这种所有代码堆在一个main函数里边了,必须得学好函数这一部分,因为必须得习惯一整个代码实现中有多个part(函数/结构体)

2.不要被吓怕,慢点来肯定会学会的,不过基础还是要打牢

3.要有把精雕细琢的成品掏空的勇气:有重头再来的勇气和决心!!

4.再多学学,写写代码吧,哎

待解决的问题:

1.关于怎么对每一个点数进行重置并计算概率???

2.对于auto这个地方,编译器通不过,可是我的版本还是改不了,调试了还是解决不了

3.DFS的实现

整体代码实现:

/*
  模拟题, 先从小到大枚举重置的筛子个数, 
  再枚举重置哪些筛子, 
  对每一种情况计算一个成功的概率, 
  为方便判断概率大小可以增加筛子数时给已有答案分子分母同时 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;
	
}
 
 //DFS算法 
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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值