题目: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;
}