保龄球(模拟退火)

题目:https://www.acwing.com/problem/content/2426/

题意:保龄球游戏:总共n轮,但是可能有第n+1轮,加赛情况。
1、“全中”:如果选手第一次尝试就击倒了全部 10 个木瓶,那么这一轮就称为“全中”。在一个“全中”轮中,由于所有木瓶在第一次尝试中都已经被击倒,所以选手不需要再进行第二次投球尝试。同时,在计算总分时,选手在下一轮的得分将会被乘 2 计入总分。
2、“补中”:如果选手使用两次尝试击倒了 10 个木瓶,那么这一轮就称为“补中”。同时,在计算总分时,选手在下一轮中的第一次尝试的得分将会被乘以 2 计入总分。
3、“失误”:如果选手未能通过两次尝试击倒全部的木瓶,那么这一轮就被称为“失误”。同时,在计算总分时,选手在下一轮的得分会被计入总分,没有分数被翻倍。
此外,如果第 N 轮是“全中”,那么选手可以进行一次附加轮:也就是,如果第 N 轮是“全中”,那么选手将一共进行 N+1 轮比赛,显然,第n+1轮比赛结果一定会翻倍,但没有第n+2轮,加赛只执行一次。

现在给你一个初始每一轮的结果,如果有n+1轮,也会输入n+1轮,让后让你利用翻倍规则,对这n轮或者n+1轮进行重新排序,然后最大化所有轮的分数和,当然,重新排序后的轮数也得满足上面的要求,和初始轮数一样。

解法:模拟退火。这个题符合在一个集合空间里面找一个最优解的方法,而且check去判断得分的时候,时间复杂度比较小,因为轮数比较小,比较符合模拟退火。
首先,每一个排列可以看成一个自变量,每一自变量下都有一个得分,可以看做一个函数,随机枚举这个情况下的周围情况的时候,可以随机交换两个回合,然后判断分数是否增加,当然交换的前提是得满足轮数不能因为交换而改变,然后就是模拟退火的套用。

#include <algorithm>
#include <bitset>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <functional>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
//#include <unordered_map>
//#include <unordered_set>
//#include <bits/stdc++.h>
//#define int long long
#define pb push_back
#define pii pair<int, int>
#define mpr make_pair
#define ms(a, b) memset((a), (b), sizeof(a))
#define x first
#define y second
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;
inline int read() {
    char ch = getchar();
    int s = 0, w = 1;
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = s * 10 + ch - '0', ch = getchar();
    }
    return s * w;
}
const int N = 55;
int n, m;
pii q[N];
int ans;

int calc() {
    int res = 0;
    for (int i = 0; i < m; i++) {
        res += q[i].x + q[i].y;//加上每轮得分
        if (i < n) {
            if (q[i].x == 10)//下一轮所有加倍,再加一遍
                res += q[i + 1].x + q[i + 1].y;
            else if (q[i].x + q[i].y == 10)//下一轮第一次加倍
                res += q[i + 1].x;
        }
    }
    ans = max(ans, res);//取最大
    return res;
}
//模拟退火
void simulate_anneal() {
    //
    for (double t = 1e4; t > 1e-4; t *= 0.99) {
        //这里的t没有代表步长,每次在这m轮里面随机挑选两轮
        int a = rand() % m, b = rand() % m;
        int x = calc();//先判断目前的排列的得分
        swap(q[a], q[b]); //进行交换
        if (n + (q[n - 1].x == 10) == m) {//判断交换完轮数是否改变
            int y = calc();//得到现在得分
            int delta = y - x;//作差
            // 不成立情况
            //t用来判断以什么样的概率跳过去,因为是求最大值,所以没-号了
            if (exp(delta / t) < (double)rand() / RAND_MAX) swap(q[a], q[b]);
        } else
            swap(q[a], q[b]);
    }
}
signed main() {
    n = read();
    for (int i = 0; i < n; i++) q[i].x = read(), q[i].y = read();
    if (q[n - 1].x == 10)//判断是否有第n轮,因为下标从0开始了,并且确定轮数m
        m = n + 1, q[n].x = read(), q[n].y = read();
    else
        m = n;
    int kkk = 100;//一般暴力跑100次就够了,而且不超时,但得看具体题目,具体方法,以及calc的时间复杂度
    while (kkk--)
        //害怕超时的话,可以用卡时法,因为也是随机跑的,时间复杂度不太确定,可以用这个方法
        // while ((double)clock() / CLOCKS_PER_SEC < 0.8)
        simulate_anneal();
    cout << ans << endl;
    return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值