首先先来看看题目,这次我真的一点思路也没有了,晕(痛苦),题目如下:
输入样例:
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;
}