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