[AHOI2014/JSOI2014]保龄球

众所周知,模拟退火这个东西是要看 \(rp\) 的。

题目链接


首先,理解题意,有以下规则:

  • 每轮的得分为两次所扔中瓶子的数量之和。
  • 每轮共计十个瓶,可以扔次,如果第一次扔满,就没有第二次,同时会多奖励下一轮的总得分(即“全中”)。
  • 如果两次一共扔满十个球,就多奖励下一轮的第一次得分(即“补中”)注意该操作不与“全中”重复计算
  • 如果最后一轮出现“全中”,可再来一轮,但仅限一次。不管下次如何,都不再来。

理解题意之后就好做了,直接模拟退火

因为游戏轮数保持不变,只有每轮得分顺序发生改变。所以我们可以先保持输入顺序不变,每次枚举任意两轮,暴力计算两轮分数交换后的得分情况。如果得分增大,就直接交换;如果没有,就以概率决定是否交换。记得答案要取最大值。

最后要注意的就是:

  • 输入时可以从 \(0\) 开始,方便后面随机取模得到轮数。
  • 在交换得分顺序后,要判断最后一轮的情况(当前状态的游戏轮数)是否满足初始状态的游戏轮数。
  • 因为是模拟退火嘛,所以直接卡时就好了,\(0.8s\) 以内应该都可以的。

输出答案,就完事啦。


代码

#include<cstdio>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cstdlib>
using namespace std;
#define rt register int
const int N = 55;
int n,m,a,b,ans,tmp,res,tim,num;
char s;
struct node {
	int x,y;
}g[N];
inline void read(int &x) {
	x = 0, s = getchar();
	while(s < '0' || s > '9') s = getchar();
	while(s <= '9' && s >= '0') x = x * 10 + s - '0', s = getchar();
} 
inline int cale() {
	res = 0;
	for(rt i = 0; i < m; i ++) {
		res += g[i].x + g[i].y;
		if(i < n) {
			if(g[i].x == 10) res +=  g[i + 1].x + g[i + 1].y;
			else if(g[i].x + g[i].y == 10) res += g[i + 1].x;
		}
	}
	ans = max(ans,res);
	return res;
} 
inline void simulate_anneal() {
	for(double i = 1e4; i > 1e-6; i = i * 0.999) {
		a = rand() % m, b = rand() % m;
		num = cale();
		swap(g[a],g[b]);
		if(n + (g[n - 1].x == 10) == m) {
			tim = cale(), tmp = tim - num;
			if(exp(tmp / i) < (double)rand() / RAND_MAX) swap(g[a],g[b]);
		}
		else swap(g[a],g[b]);
	}
}
int main() {
	read(n);
	m = n;
	for(rt i = 0; i < n; i ++) read(g[i].x), read(g[i].y);
	if(g[n - 1].x == 10) read(g[n].x), read(g[n].y), m++;
	while((double)clock() / CLOCKS_PER_SEC < 0.2) simulate_anneal();
	printf("%d",ans);
	return 0;
}
posted @ 2021-01-18 16:17  Spring-Araki  阅读(84)  评论(0编辑  收藏  举报